From 9d63882e743394719054d1e61abf12a39ac75b23 Mon Sep 17 00:00:00 2001 From: Minh2010 Date: Thu, 25 Sep 2025 18:46:02 +0700 Subject: [PATCH 1/7] Hot Fix Ditthering and CheckerBoardBackground --- Extension/scripts/Auto-Image.js | 511 +++++++++++++++++--------------- Extension/themes/classic.css | 99 ++++--- 2 files changed, 321 insertions(+), 289 deletions(-) diff --git a/Extension/scripts/Auto-Image.js b/Extension/scripts/Auto-Image.js index fa8c9436..20fc35d8 100644 --- a/Extension/scripts/Auto-Image.js +++ b/Extension/scripts/Auto-Image.js @@ -87,7 +87,7 @@ function getText(key, params) { OVERLAY: { OPACITY_DEFAULT: 0.6, BLUE_MARBLE_DEFAULT: false, - ditheringEnabled: false, + ditheringEnabled: true, }, // --- START: Color data from colour-converter.js --- // New color structure with proper ID mapping COLOR_MAP: { @@ -3410,7 +3410,7 @@ function getText(key, params) { autoSwapToggle.addEventListener('change', (e) => { CONFIG.autoSwap = e.target.checked; console.log(`🔄 Auto-swap ${CONFIG.autoSwap ? 'enabled' : 'disabled'}`); - + // Handle autoBuy toggle dependency const autoBuyToggle = statsContainer.querySelector('#autoBuyToggle'); if (autoBuyToggle) { @@ -3433,7 +3433,7 @@ function getText(key, params) { if (autoBuyToggle) { autoBuyToggle.checked = CONFIG.autoBuyToggle; autoBuyToggle.disabled = !CONFIG.autoSwap; // Disable if autoSwap is off - + autoBuyToggle.addEventListener('change', (e) => { CONFIG.autoBuyToggle = e.target.checked; console.log(`💰 Auto-buy ${CONFIG.autoBuyToggle ? 'enabled' : 'disabled'}`); @@ -4701,7 +4701,7 @@ function getText(key, params) { canvasStack.style.width = newWidth + 'px'; canvasStack.style.height = newHeight + 'px'; baseCtx.imageSmoothingEnabled = false; - + if (!state.availableColors || state.availableColors.length === 0) { if (baseProcessor !== processor && (!baseProcessor.img || !baseProcessor.canvas)) { await baseProcessor.load(); @@ -4714,7 +4714,7 @@ function getText(key, params) { updateZoomLayout(); return; } - + if (baseProcessor !== processor && (!baseProcessor.img || !baseProcessor.canvas)) { await baseProcessor.load(); } @@ -5690,26 +5690,26 @@ function getText(key, params) { Utils.showAlert('No image available for editing. Please upload an image first.', 'error'); return; } - + // Hide resize panel resizeContainer.style.display = 'none'; - + // Create edit panel if it doesn't exist let editOverlay = document.getElementById('editOverlay'); if (!editOverlay) { createEditPanel(); editOverlay = document.getElementById('editOverlay'); } - + // Get current image data from baseCanvas const imageData = baseCanvas.toDataURL(); - + // Initialize edit panel with current image initializeEditPanel(imageData); - + // Show edit panel editOverlay.style.display = 'block'; - + console.log('✨ Pixel Art Editor opened successfully'); } catch (error) { console.error('Error opening pixel art editor:', error); @@ -5721,7 +5721,7 @@ function getText(key, params) { const editOverlay = document.createElement('div'); editOverlay.id = 'editOverlay'; editOverlay.className = 'edit-overlay'; - + editOverlay.innerHTML = `
@@ -5830,9 +5830,9 @@ function getText(key, params) {
`; - + document.body.appendChild(editOverlay); - + // Set up event handlers setupEditPanelEvents(); } @@ -5856,69 +5856,69 @@ function getText(key, params) { const zoomFit = document.getElementById('zoomFit'); const zoom100 = document.getElementById('zoom100'); const minimapCanvas = document.getElementById('minimapCanvas'); - + // Back to resize panel editBackBtn.onclick = () => { document.getElementById('editOverlay').style.display = 'none'; resizeContainer.style.display = 'block'; }; - + // Apply changes editApplyBtn.onclick = () => { applyEditChanges(); document.getElementById('editOverlay').style.display = 'none'; resizeContainer.style.display = 'block'; }; - + // Tool selection paintBrush.onclick = () => { selectTool('paint'); }; - + eraseTool.onclick = () => { selectTool('erase'); }; - + eyedropperTool.onclick = () => { selectTool('eyedropper'); }; - + fillTool.onclick = () => { selectTool('fill'); }; - + // Grid toggle showGrid.onclick = () => { editState.showGrid = !editState.showGrid; showGrid.classList.toggle('active', editState.showGrid); redrawCanvas(); }; - + // Brush size brushSize.oninput = () => { brushSizeValue.textContent = brushSize.value; updateBrushSize(parseInt(brushSize.value)); }; - + // Undo/Redo undoBtn.onclick = () => undoEdit(); redoBtn.onclick = () => redoEdit(); - + // Reset view resetViewBtn.onclick = () => resetEditView(); - + // Enhanced zoom controls editZoomIn.onclick = () => zoomIn(); editZoomOut.onclick = () => zoomOut(); - + zoomSelect.onchange = () => { const newZoom = parseFloat(zoomSelect.value); setZoom(newZoom); }; - + zoomFit.onclick = () => fitToWindow(); zoom100.onclick = () => setZoom(1); - + // Minimap navigation if (minimapCanvas) { minimapCanvas.onclick = (e) => navigateToMinimapPosition(e); @@ -5955,23 +5955,23 @@ function getText(key, params) { function calculateOptimalPanelSize(imageWidth, imageHeight) { const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; - + // Reserve minimal space for UI elements for almost fullscreen experience const uiReserved = { header: 80, - toolbar: 60, + toolbar: 60, bottomBar: 120, padding: 40 }; - + const maxCanvasWidth = viewportWidth - uiReserved.padding; const maxCanvasHeight = viewportHeight - uiReserved.header - uiReserved.toolbar - uiReserved.bottomBar - uiReserved.padding; - + // Calculate optimal initial zoom to fit image const scaleX = maxCanvasWidth / imageWidth; const scaleY = maxCanvasHeight / imageHeight; const initialZoom = Math.min(scaleX, scaleY, 1); // Don't zoom in initially - + return { panelWidth: Math.min(viewportWidth * 0.95, imageWidth * initialZoom + uiReserved.colorPanel + uiReserved.padding), panelHeight: Math.min(viewportHeight * 0.95, imageHeight * initialZoom + uiReserved.toolbar + uiReserved.statusBar + uiReserved.canvasToolbar + uiReserved.padding), @@ -5984,7 +5984,7 @@ function getText(key, params) { function initializeEditPanel(imageData) { const editCanvas = document.getElementById('editCanvas'); const ctx = editCanvas.getContext('2d'); - + // Reset edit state editState.zoom = 1; editState.panX = 0; @@ -5997,19 +5997,19 @@ function getText(key, params) { editState.isPanning = false; editState.isDrawing = false; editState.lastPaintPos = null; - + // Set canvas size to match baseCanvas editCanvas.width = baseCanvas.width; editCanvas.height = baseCanvas.height; editState.canvasWidth = editCanvas.width; editState.canvasHeight = editCanvas.height; - + // Configure canvas context for pixel art ctx.imageSmoothingEnabled = false; ctx.webkitImageSmoothingEnabled = false; ctx.mozImageSmoothingEnabled = false; ctx.msImageSmoothingEnabled = false; - + // Calculate optimal panel size const panelSize = calculateOptimalPanelSize(editCanvas.width, editCanvas.height); const editContainer = document.querySelector('.edit-container'); @@ -6017,44 +6017,45 @@ function getText(key, params) { editContainer.style.width = panelSize.panelWidth + 'px'; editContainer.style.height = panelSize.panelHeight + 'px'; } - + // Setup canvas container setupCanvasContainer(); - + // Load image onto canvas const img = new Image(); img.onload = () => { ctx.clearRect(0, 0, editCanvas.width, editCanvas.height); + drawCheckerboardBackground(ctx, editCanvas.width, editCanvas.height); ctx.drawImage(img, 0, 0); - - // Always fit artwork to the visible area on start and center - fitToWindow(); - centerCanvas(); - + + // Always fit artwork to the visible area on start and center + fitToWindow(); + centerCanvas(); + // Setup minimap setupMinimap(); - + // Save initial state for undo saveEditState(); - + // Set up canvas drawing events setupCanvasDrawing(); - + // Initialize color palette initializeEditColorPalette(); - + // Setup keyboard shortcuts setupKeyboardShortcuts(); - + // Setup touch support setupTouchSupport(); - + // Set initial tool selectTool('paint'); - + // Update status bar updateStatusBar(0, 0); - + // Center canvas initially centerCanvas(); }; @@ -6066,43 +6067,43 @@ function getText(key, params) { const canvas = document.getElementById('editCanvas'); const wrapper = document.getElementById('editCanvasWrapper'); const container = document.getElementById('editCanvasContainer'); - + if (!canvas || !wrapper || !container) return null; - + // Get the actual canvas element bounds (after CSS transform) const canvasRect = canvas.getBoundingClientRect(); - + // Calculate relative position within the actual canvas bounds const relativeX = (clientX - canvasRect.left) / canvasRect.width * canvas.width; const relativeY = (clientY - canvasRect.top) / canvasRect.height * canvas.height; - + // Convert to canvas coordinates const canvasX = Math.floor(relativeX); const canvasY = Math.floor(relativeY); - + // Bounds checking if (canvasX < 0 || canvasX >= canvas.width || canvasY < 0 || canvasY >= canvas.height) { return null; } - + return { x: canvasX, y: canvasY }; } function setupCanvasDrawing() { const editCanvas = document.getElementById('editCanvas'); const ctx = editCanvas.getContext('2d'); - + let isDrawing = false; let isPanning = false; let lastX = 0; let lastY = 0; let panStartX = 0; let panStartY = 0; - + const getMousePos = (e) => { return mapClientToCanvas(e.clientX, e.clientY); }; - + editCanvas.onmousedown = (e) => { if (e.button === 2 || e.ctrlKey) { // Right click or Ctrl+click for panning editState.isPanning = true; @@ -6112,38 +6113,38 @@ function getText(key, params) { e.preventDefault(); return; } - + const pos = getMousePos(e); if (!pos) return; - + if (editState.currentTool === 'eyedropper') { handleEyedropper(pos.x, pos.y); return; } - + if (editState.currentTool === 'fill') { floodFill(pos.x, pos.y, editState.currentColor); saveEditState(); return; } - + editState.isDrawing = true; editState.lastPaintPos = { x: pos.x, y: pos.y }; lastX = pos.x; lastY = pos.y; - + paintAtPosition(pos.x, pos.y, true); }; - + editCanvas.onmousemove = (e) => { const pos = getMousePos(e); - + if (pos) { editState.mouseX = pos.x; editState.mouseY = pos.y; updateStatusBar(pos.x, pos.y); } - + if (editState.isPanning) { editState.panX = e.clientX - panStartX; editState.panY = e.clientY - panStartY; @@ -6152,64 +6153,64 @@ function getText(key, params) { updateMinimap(); return; } - + if (!editState.isDrawing) { return; } - + if (pos && editState.lastPaintPos) { paintAtPosition(pos.x, pos.y, true); editState.lastPaintPos = { x: pos.x, y: pos.y }; } }; - + editCanvas.onmouseup = (e) => { if (editState.isPanning) { editState.isPanning = false; selectTool(editState.currentTool); // Restore cursor return; } - + if (editState.isDrawing) { editState.isDrawing = false; editState.lastPaintPos = null; saveEditState(); } }; - + editCanvas.onmouseleave = () => { if (editState.isPanning) { editState.isPanning = false; selectTool(editState.currentTool); // Restore cursor } - + if (editState.isDrawing) { editState.isDrawing = false; editState.lastPaintPos = null; saveEditState(); } }; - + // Prevent context menu on right click editCanvas.oncontextmenu = (e) => { e.preventDefault(); return false; }; - + // Enhanced zoom with mouse wheel (zoom to cursor) editCanvas.onwheel = (e) => { e.preventDefault(); - const zoomFactor = e.deltaY < 0 ? 1.2 : 1/1.2; + const zoomFactor = e.deltaY < 0 ? 1.2 : 1 / 1.2; zoomToPoint(editState.zoom * zoomFactor, e.clientX, e.clientY); }; } function paintAtPosition(x, y, isMouseDown = true) { if (!isMouseDown || !editState.currentColor) return; - + const canvas = document.getElementById('editCanvas'); const ctx = canvas.getContext('2d'); - + if (editState.lastPaintPos && editState.currentTool === 'paint') { // Draw line from last position to current position drawLine(ctx, editState.lastPaintPos.x, editState.lastPaintPos.y, x, y, editState.currentTool); @@ -6217,9 +6218,9 @@ function getText(key, params) { // Single brush stroke drawBrush(ctx, x, y, editState.currentTool); } - + editState.lastPaintPos = { x, y }; - + // Update minimap thumbnail if (!editState.updatePending) { editState.updatePending = true; @@ -6233,7 +6234,7 @@ function getText(key, params) { function drawBrush(ctx, x, y, tool) { const size = editState.brushSize; const halfSize = Math.floor(size / 2); - + if (tool === 'paint') { ctx.fillStyle = editState.currentColor; for (let dx = 0; dx < size; dx++) { @@ -6241,7 +6242,7 @@ function getText(key, params) { // Center the brush at the cursor position const px = x - halfSize + dx; const py = y - halfSize + dy; - + if (px >= 0 && px < ctx.canvas.width && py >= 0 && py < ctx.canvas.height) { ctx.fillRect(px, py, 1, 1); } @@ -6253,7 +6254,7 @@ function getText(key, params) { // Center the brush at the cursor position const px = x - halfSize + dx; const py = y - halfSize + dy; - + if (px >= 0 && px < ctx.canvas.width && py >= 0 && py < ctx.canvas.height) { ctx.clearRect(px, py, 1, 1); } @@ -6269,16 +6270,16 @@ function getText(key, params) { function erasePixel(x, y) { const editCanvas = document.getElementById('editCanvas'); const ctx = editCanvas.getContext('2d'); - + const size = editState.brushSize; const halfSize = Math.floor(size / 2); - + for (let dx = 0; dx < size; dx++) { for (let dy = 0; dy < size; dy++) { // Center the brush at the cursor position (consistent with drawBrush) const px = x - halfSize + dx; const py = y - halfSize + dy; - + if (px >= 0 && px < editCanvas.width && py >= 0 && py < editCanvas.height) { ctx.clearRect(px, py, 1, 1); } @@ -6293,15 +6294,15 @@ function getText(key, params) { const sx = x1 < x2 ? 1 : -1; const sy = y1 < y2 ? 1 : -1; let err = dx - dy; - + let x = x1; let y = y1; - + while (true) { drawBrush(ctx, x, y, tool); - + if (x === x2 && y === y2) break; - + const e2 = 2 * err; if (e2 > -dy) { err -= dy; @@ -6321,15 +6322,15 @@ function getText(key, params) { const sx = x1 < x2 ? 1 : -1; const sy = y1 < y2 ? 1 : -1; let err = dx - dy; - + let x = x1; let y = y1; - + while (true) { erasePixel(x, y); - + if (x === x2 && y === y2) break; - + const e2 = 2 * err; if (e2 > -dy) { err -= dy; @@ -6344,14 +6345,14 @@ function getText(key, params) { function selectTool(tool) { editState.currentTool = tool; - + document.querySelectorAll('.edit-tool').forEach(btn => { btn.classList.remove('active'); }); - + const editCanvas = document.getElementById('editCanvas'); - - switch(tool) { + + switch (tool) { case 'paint': document.getElementById('paintBrush').classList.add('active'); editCanvas.style.cursor = 'crosshair'; @@ -6378,18 +6379,18 @@ function getText(key, params) { function initializeEditColorPalette() { const colorGrid = document.getElementById('editColorGrid'); const currentColorDisplay = document.getElementById('currentColorDisplay'); - + colorGrid.innerHTML = ''; - + let availableColors = []; - + // Try to get colors from state first if (state && state.availableColors && state.availableColors.length > 0) { availableColors = state.availableColors.map(color => ({ id: color.id, name: color.name, rgb: color.rgb, - hex: `#${color.rgb[0].toString(16).padStart(2,'0')}${color.rgb[1].toString(16).padStart(2,'0')}${color.rgb[2].toString(16).padStart(2,'0')}` + hex: `#${color.rgb[0].toString(16).padStart(2, '0')}${color.rgb[1].toString(16).padStart(2, '0')}${color.rgb[2].toString(16).padStart(2, '0')}` })); } else { // Fallback to CONFIG.COLOR_MAP @@ -6399,27 +6400,27 @@ function getText(key, params) { id: color.id, name: color.name, rgb: [color.rgb.r, color.rgb.g, color.rgb.b], - hex: `#${color.rgb.r.toString(16).padStart(2,'0')}${color.rgb.g.toString(16).padStart(2,'0')}${color.rgb.b.toString(16).padStart(2,'0')}` + hex: `#${color.rgb.r.toString(16).padStart(2, '0')}${color.rgb.g.toString(16).padStart(2, '0')}${color.rgb.b.toString(16).padStart(2, '0')}` })); } - + // Fallback to basic colors if nothing available if (availableColors.length === 0) { availableColors = [ - {id: 0, name: 'Black', rgb: [0,0,0], hex: '#000000'}, - {id: 1, name: 'White', rgb: [255,255,255], hex: '#ffffff'}, - {id: 2, name: 'Red', rgb: [255,0,0], hex: '#ff0000'}, - {id: 3, name: 'Green', rgb: [0,255,0], hex: '#00ff00'}, - {id: 4, name: 'Blue', rgb: [0,0,255], hex: '#0000ff'} + { id: 0, name: 'Black', rgb: [0, 0, 0], hex: '#000000' }, + { id: 1, name: 'White', rgb: [255, 255, 255], hex: '#ffffff' }, + { id: 2, name: 'Red', rgb: [255, 0, 0], hex: '#ff0000' }, + { id: 3, name: 'Green', rgb: [0, 255, 0], hex: '#00ff00' }, + { id: 4, name: 'Blue', rgb: [0, 0, 255], hex: '#0000ff' } ]; } - + // Update color count const colorCount = document.getElementById('editColorCount'); if (colorCount) { colorCount.textContent = availableColors.length; } - + availableColors.forEach(color => { const colorBtn = document.createElement('button'); colorBtn.className = 'color-btn'; @@ -6427,24 +6428,24 @@ function getText(key, params) { colorBtn.title = `${color.name} (${color.hex})`; colorBtn.dataset.colorId = color.id; colorBtn.dataset.colorHex = color.hex; - + colorBtn.onclick = () => { editState.currentColor = color.hex; editState.currentColorId = color.id; currentColorDisplay.style.backgroundColor = color.hex; - + // Update active color document.querySelectorAll('.color-btn').forEach(btn => { btn.classList.remove('selected'); }); colorBtn.classList.add('selected'); - + updateStatusBar(editState.mouseX, editState.mouseY); }; - + colorGrid.appendChild(colorBtn); }); - + // Set first color as default if (availableColors.length > 0) { editState.currentColor = availableColors[0].hex; @@ -6457,50 +6458,51 @@ function getText(key, params) { function saveEditState() { const editCanvas = document.getElementById('editCanvas'); const imageData = editCanvas.toDataURL(); - + editState.undoStack.push(imageData); - + // Limit undo stack size if (editState.undoStack.length > 50) { editState.undoStack.shift(); } - + // Clear redo stack when new action is performed editState.redoStack = []; - + updateUndoRedoButtons(); } function undoEdit() { if (editState.undoStack.length <= 1) return; - + const currentState = editState.undoStack.pop(); editState.redoStack.push(currentState); - + const previousState = editState.undoStack[editState.undoStack.length - 1]; loadEditState(previousState); - + updateUndoRedoButtons(); } function redoEdit() { if (editState.redoStack.length === 0) return; - + const nextState = editState.redoStack.pop(); editState.undoStack.push(nextState); - + loadEditState(nextState); - + updateUndoRedoButtons(); } function loadEditState(imageData) { const editCanvas = document.getElementById('editCanvas'); const ctx = editCanvas.getContext('2d'); - + const img = new Image(); img.onload = () => { ctx.clearRect(0, 0, editCanvas.width, editCanvas.height); + drawCheckerboardBackground(ctx, editCanvas.width, editCanvas.height); ctx.drawImage(img, 0, 0); }; img.src = imageData; @@ -6509,11 +6511,11 @@ function getText(key, params) { function updateUndoRedoButtons() { const undoBtn = document.getElementById('undoBtn'); const redoBtn = document.getElementById('redoBtn'); - + if (undoBtn) { undoBtn.disabled = editState.undoStack.length <= 1; } - + if (redoBtn) { redoBtn.disabled = editState.redoStack.length === 0; } @@ -6524,7 +6526,7 @@ function getText(key, params) { if (wrapper) { wrapper.style.transform = `translate(${editState.panX}px, ${editState.panY}px) scale(${editState.zoom})`; } - + // Update zoom select const zoomSelect = document.getElementById('zoomSelect'); if (zoomSelect) { @@ -6540,9 +6542,9 @@ function getText(key, params) { const canvasContainer = document.getElementById('editCanvasContainer'); const editCanvas = document.getElementById('editCanvas'); const wrapper = document.getElementById('editCanvasWrapper'); - + if (!canvasContainer || !wrapper) return; - + // Setup container styles canvasContainer.style.cssText = ` position: relative; @@ -6561,7 +6563,7 @@ function getText(key, params) { background-position: 0 0, 0 10px, 10px -10px, -10px 0px; background-color: #ddd; `; - + // Setup wrapper styles wrapper.style.cssText = ` position: relative; @@ -6576,28 +6578,28 @@ function getText(key, params) { function zoomToPoint(newZoom, clientX, clientY) { const container = document.getElementById('editCanvasContainer'); const wrapper = document.getElementById('editCanvasWrapper'); - + if (!container || !wrapper) return; - + newZoom = Math.max(0.1, Math.min(32, newZoom)); - + if (clientX !== undefined && clientY !== undefined) { // Get current transform values const oldZoom = editState.zoom; const rect = container.getBoundingClientRect(); - + // Calculate zoom center point relative to container const containerCenterX = rect.left + rect.width / 2; const containerCenterY = rect.top + rect.height / 2; - + // Calculate offset to keep zoom point centered const offsetX = (clientX - containerCenterX) * (1 - newZoom / oldZoom); const offsetY = (clientY - containerCenterY) * (1 - newZoom / oldZoom); - + editState.panX += offsetX; editState.panY += offsetY; } - + editState.zoom = newZoom; constrainPan(); updateCanvasTransform(); @@ -6608,20 +6610,20 @@ function getText(key, params) { function constrainPan() { const container = document.getElementById('editCanvasContainer'); const canvas = document.getElementById('editCanvas'); - + if (!container || !canvas) return; - + const containerRect = container.getBoundingClientRect(); const scaledWidth = canvas.width * editState.zoom; const scaledHeight = canvas.height * editState.zoom; - - // Small padding around edges to avoid snapping against borders - const padding = 10; - + + // Small padding around edges to avoid snapping against borders + const padding = 10; + // Calculate limits to keep canvas somewhat visible const maxPanX = Math.max(0, (scaledWidth - containerRect.width) / 2 + padding); const maxPanY = Math.max(0, (scaledHeight - containerRect.height) / 2 + padding); - + editState.panX = Math.max(-maxPanX, Math.min(maxPanX, editState.panX)); editState.panY = Math.max(-maxPanY, Math.min(maxPanY, editState.panY)); } @@ -6645,16 +6647,16 @@ function getText(key, params) { function fitToWindow() { const container = document.getElementById('editCanvasContainer'); const canvas = document.getElementById('editCanvas'); - + if (!container || !canvas) return; - + const containerRect = container.getBoundingClientRect(); const padding = 40; - + const scaleX = (containerRect.width - padding) / canvas.width; const scaleY = (containerRect.height - padding) / canvas.height; const fitZoom = Math.max(0.1, Math.min(scaleX, scaleY)); - + // Center the canvas editState.zoom = fitZoom; editState.panX = 0; @@ -6675,7 +6677,7 @@ function getText(key, params) { editState.zoom = 1; editState.panX = 0; editState.panY = 0; - + updateCanvasTransform(); updateMinimap(); } @@ -6684,27 +6686,28 @@ function getText(key, params) { const minimapCanvas = document.getElementById('minimapCanvas'); const editCanvas = document.getElementById('editCanvas'); const minimapContainer = document.getElementById('minimapContainer'); - + if (!minimapCanvas || !editCanvas) return; - + // Show minimap only for larger images if (editCanvas.width < 100 || editCanvas.height < 100) { if (minimapContainer) minimapContainer.style.display = 'none'; return; } - + // Calculate minimap size const maxSize = 150; const scale = Math.min(maxSize / editCanvas.width, maxSize / editCanvas.height); - + minimapCanvas.width = editCanvas.width * scale; minimapCanvas.height = editCanvas.height * scale; - + // Draw thumbnail const minimapCtx = minimapCanvas.getContext('2d'); minimapCtx.imageSmoothingEnabled = false; + drawCheckerboardBackground(ctx, editCanvas.width, editCanvas.height); minimapCtx.drawImage(editCanvas, 0, 0, minimapCanvas.width, minimapCanvas.height); - + // Update viewport indicator updateMinimap(); } @@ -6714,19 +6717,19 @@ function getText(key, params) { const minimapCanvas = document.getElementById('minimapCanvas'); const container = document.getElementById('editCanvasContainer'); const editCanvas = document.getElementById('editCanvas'); - + if (!viewport || !minimapCanvas || !container || !editCanvas) return; - + const containerRect = container.getBoundingClientRect(); const scale = minimapCanvas.width / editCanvas.width; - + // Calculate visible area in minimap coordinates const visibleWidth = Math.min(containerRect.width / editState.zoom * scale, minimapCanvas.width); const visibleHeight = Math.min(containerRect.height / editState.zoom * scale, minimapCanvas.height); - + const viewportX = (minimapCanvas.width / 2) - (editState.panX / editState.zoom * scale) - (visibleWidth / 2); const viewportY = (minimapCanvas.height / 2) - (editState.panY / editState.zoom * scale) - (visibleHeight / 2); - + viewport.style.width = `${visibleWidth}px`; viewport.style.height = `${visibleHeight}px`; viewport.style.left = `${Math.max(0, Math.min(minimapCanvas.width - visibleWidth, viewportX))}px`; @@ -6736,20 +6739,20 @@ function getText(key, params) { function navigateToMinimapPosition(e) { const minimapCanvas = document.getElementById('minimapCanvas'); const editCanvas = document.getElementById('editCanvas'); - + if (!minimapCanvas || !editCanvas) return; - + const rect = minimapCanvas.getBoundingClientRect(); const x = (e.clientX - rect.left) / minimapCanvas.width; const y = (e.clientY - rect.top) / minimapCanvas.height; - + // Convert to canvas coordinates and center const targetX = (x - 0.5) * editCanvas.width * editState.zoom; const targetY = (y - 0.5) * editCanvas.height * editState.zoom; - + editState.panX = -targetX; editState.panY = -targetY; - + constrainPan(); updateCanvasTransform(); updateMinimap(); @@ -6766,18 +6769,18 @@ function getText(key, params) { function handleEyedropper(x, y) { const editCanvas = document.getElementById('editCanvas'); const ctx = editCanvas.getContext('2d'); - + if (x >= 0 && x < editCanvas.width && y >= 0 && y < editCanvas.height) { const imageData = ctx.getImageData(x, y, 1, 1); const [r, g, b] = imageData.data; - const pickedColor = `#${r.toString(16).padStart(2,'0')}${g.toString(16).padStart(2,'0')}${b.toString(16).padStart(2,'0')}`; - + const pickedColor = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; + editState.currentColor = pickedColor; const currentColorDisplay = document.getElementById('currentColorDisplay'); if (currentColorDisplay) { currentColorDisplay.style.backgroundColor = pickedColor; } - + // Try to find matching color in palette document.querySelectorAll('.edit-color-btn').forEach(btn => { btn.classList.remove('active'); @@ -6786,7 +6789,7 @@ function getText(key, params) { editState.currentColorId = parseInt(btn.dataset.colorId); } }); - + updateStatusBar(x, y); } } @@ -6796,54 +6799,54 @@ function getText(key, params) { const ctx = editCanvas.getContext('2d'); const imageData = ctx.getImageData(0, 0, editCanvas.width, editCanvas.height); const data = imageData.data; - + if (startX < 0 || startX >= editCanvas.width || startY < 0 || startY >= editCanvas.height) return; - + const startIndex = (startY * editCanvas.width + startX) * 4; const startR = data[startIndex]; const startG = data[startIndex + 1]; const startB = data[startIndex + 2]; const startA = data[startIndex + 3]; - + // Convert fill color to RGB const fillR = parseInt(fillColor.slice(1, 3), 16); const fillG = parseInt(fillColor.slice(3, 5), 16); const fillB = parseInt(fillColor.slice(5, 7), 16); - + // Don't fill if the color is already the same if (startR === fillR && startG === fillG && startB === fillB) return; - - const pixelsToCheck = [{x: startX, y: startY}]; + + const pixelsToCheck = [{ x: startX, y: startY }]; const checkedPixels = new Set(); - + while (pixelsToCheck.length > 0) { - const {x, y} = pixelsToCheck.pop(); + const { x, y } = pixelsToCheck.pop(); const key = `${x},${y}`; - + if (checkedPixels.has(key)) continue; checkedPixels.add(key); - + if (x < 0 || x >= editCanvas.width || y < 0 || y >= editCanvas.height) continue; - + const index = (y * editCanvas.width + x) * 4; const r = data[index]; const g = data[index + 1]; const b = data[index + 2]; const a = data[index + 3]; - + if (r === startR && g === startG && b === startB && a === startA) { data[index] = fillR; data[index + 1] = fillG; data[index + 2] = fillB; data[index + 3] = 255; - - pixelsToCheck.push({x: x + 1, y}); - pixelsToCheck.push({x: x - 1, y}); - pixelsToCheck.push({x, y: y + 1}); - pixelsToCheck.push({x, y: y - 1}); + + pixelsToCheck.push({ x: x + 1, y }); + pixelsToCheck.push({ x: x - 1, y }); + pixelsToCheck.push({ x, y: y + 1 }); + pixelsToCheck.push({ x, y: y - 1 }); } } - + ctx.putImageData(imageData, 0, 0); } @@ -6854,38 +6857,54 @@ function getText(key, params) { function drawGrid(ctx, width, height) { if (!editState.showGrid || editState.zoom < 4) return; - + ctx.save(); ctx.strokeStyle = 'rgba(128, 128, 128, 0.3)'; ctx.lineWidth = 1 / editState.zoom; - + for (let x = 0; x <= width; x++) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, height); ctx.stroke(); } - + for (let y = 0; y <= height; y++) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke(); } - + ctx.restore(); } + function drawCheckerboardBackground(ctx, width, height) { + const checkerSize = 8; // Size of each checker square + ctx.fillStyle = '#f0f0f0'; // Light gray + ctx.fillRect(0, 0, width, height); + + ctx.fillStyle = '#e0e0e0'; // Slightly darker gray + for (let x = 0; x < width; x += checkerSize) { + for (let y = 0; y < height; y += checkerSize) { + if ((Math.floor(x / checkerSize) + Math.floor(y / checkerSize)) % 2 === 1) { + ctx.fillRect(x, y, checkerSize, checkerSize); + } + } + } + } + function redrawCanvas() { if (editState.undoStack.length === 0) return; - + const currentState = editState.undoStack[editState.undoStack.length - 1]; const editCanvas = document.getElementById('editCanvas'); const ctx = editCanvas.getContext('2d'); - + const img = new Image(); img.onload = () => { ctx.clearRect(0, 0, editCanvas.width, editCanvas.height); + drawCheckerboardBackground(ctx, editCanvas.width, editCanvas.height); ctx.drawImage(img, 0, 0); drawGrid(ctx, editCanvas.width, editCanvas.height); }; @@ -6895,17 +6914,17 @@ function getText(key, params) { function setupTouchSupport() { const canvas = document.getElementById('editCanvas'); if (!canvas) return; - + let touchStartTime = 0; - + canvas.addEventListener('touchstart', handleTouchStart, { passive: false }); canvas.addEventListener('touchmove', handleTouchMove, { passive: false }); canvas.addEventListener('touchend', handleTouchEnd, { passive: false }); - + function handleTouchStart(e) { e.preventDefault(); touchStartTime = Date.now(); - + if (e.touches.length === 1) { // Single touch - start painting const touch = e.touches[0]; @@ -6919,22 +6938,22 @@ function getText(key, params) { // Two finger - prepare for zoom/pan const touch1 = e.touches[0]; const touch2 = e.touches[1]; - + editState.lastTouchDistance = Math.hypot( touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY ); - + editState.lastTouchCenter = { x: (touch1.clientX + touch2.clientX) / 2, y: (touch1.clientY + touch2.clientY) / 2 }; } } - + function handleTouchMove(e) { e.preventDefault(); - + if (e.touches.length === 1 && editState.isDrawing) { // Continue painting const touch = e.touches[0]; @@ -6946,46 +6965,46 @@ function getText(key, params) { // Pinch zoom and pan const touch1 = e.touches[0]; const touch2 = e.touches[1]; - + const currentDistance = Math.hypot( touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY ); - + const currentCenter = { x: (touch1.clientX + touch2.clientX) / 2, y: (touch1.clientY + touch2.clientY) / 2 }; - + // Zoom based on distance change if (editState.lastTouchDistance > 0) { const zoomFactor = currentDistance / editState.lastTouchDistance; const newZoom = Math.max(0.1, Math.min(32, editState.zoom * zoomFactor)); zoomToPoint(newZoom, currentCenter.x, currentCenter.y); } - + // Pan based on center movement editState.panX += currentCenter.x - editState.lastTouchCenter.x; editState.panY += currentCenter.y - editState.lastTouchCenter.y; - + editState.lastTouchDistance = currentDistance; editState.lastTouchCenter = currentCenter; - + constrainPan(); updateCanvasTransform(); updateMinimap(); } } - + function handleTouchEnd(e) { e.preventDefault(); - + if (editState.isDrawing) { editState.isDrawing = false; editState.lastPaintPos = null; saveEditState(); } - + if (e.touches.length === 0) { editState.lastTouchDistance = 0; } @@ -6997,14 +7016,14 @@ function getText(key, params) { // Only handle shortcuts when edit panel is visible const editOverlay = document.getElementById('editOverlay'); if (!editOverlay || editOverlay.style.display === 'none') return; - + // Prevent default for handled keys const handledKeys = ['b', 'e', 'i', 'f', 'g', 'z', '[', ']']; if (handledKeys.includes(e.key.toLowerCase()) || (e.ctrlKey && e.key.toLowerCase() === 'z')) { e.preventDefault(); } - - switch(e.key.toLowerCase()) { + + switch (e.key.toLowerCase()) { case 'b': // Brush selectTool('paint'); break; @@ -7079,34 +7098,34 @@ function getText(key, params) { if (!editCanvas) { throw new Error('Edit canvas not found'); } - + // Find the resize canvas in the resize panel const resizeCanvas = document.getElementById('resizeCanvas'); if (!resizeCanvas) { throw new Error('Resize canvas not found'); } - + const baseCtx = resizeCanvas.getContext('2d'); if (!baseCtx) { throw new Error('Resize canvas context not available'); } - + // Make sure the resize canvas has the same dimensions as the edit canvas if (resizeCanvas.width !== editCanvas.width || resizeCanvas.height !== editCanvas.height) { resizeCanvas.width = editCanvas.width; resizeCanvas.height = editCanvas.height; } - + // Clear resize canvas baseCtx.clearRect(0, 0, resizeCanvas.width, resizeCanvas.height); - + // Copy edited image to resize canvas baseCtx.imageSmoothingEnabled = false; baseCtx.drawImage(editCanvas, 0, 0); - + // CRITICAL: Completely replace the template system with edited artwork const editedImageData = editCanvas.toDataURL(); - + // Create new processor with edited image as the template if (window.WPlaceImageProcessor) { const newProcessor = new window.WPlaceImageProcessor(editedImageData); @@ -7115,7 +7134,7 @@ function getText(key, params) { const editCtx = editCanvas.getContext('2d'); const editImageData = editCtx.getImageData(0, 0, editCanvas.width, editCanvas.height); const pixels = editImageData.data; - + // Count valid pixels in the edited image let totalValidPixels = 0; for (let i = 0; i < pixels.length; i += 4) { @@ -7123,36 +7142,36 @@ function getText(key, params) { const r = pixels[i]; const g = pixels[i + 1]; const b = pixels[i + 2]; - + const isTransparent = !state.paintTransparentPixels && a < state.customTransparencyThreshold; const isWhiteAndSkipped = !state.paintWhitePixels && Utils.isWhitePixel(r, g, b); - + if (!isTransparent && !isWhiteAndSkipped) { totalValidPixels++; } } - + // COMPLETELY REBUILD state.imageData with the edited artwork state.imageData = { width: editCanvas.width, - height: editCanvas.height, + height: editCanvas.height, pixels: pixels, totalPixels: totalValidPixels, processor: newProcessor, }; - + // CRITICAL: Update state.originalImage so resize panel uses edited artwork as base template state.originalImage = { dataUrl: editedImageData, width: editCanvas.width, height: editCanvas.height }; - + // Update state with new totals state.totalPixels = totalValidPixels; state.paintedPixels = 0; // Reset progress since this is a new template state.imageLoaded = true; - + // Update local processors if (typeof processor !== 'undefined') { processor = newProcessor; @@ -7160,38 +7179,38 @@ function getText(key, params) { } if (typeof baseProcessor !== 'undefined') { baseProcessor = newProcessor; - console.log('🔄 Updated baseProcessor with edited artwork'); + console.log('🔄 Updated baseProcessor with edited artwork'); } - + // Force regeneration of overlays by clearing cached mask data if (typeof window._maskImageData !== 'undefined') { delete window._maskImageData; } - + // Update UI to reflect the new template if (typeof updateUI === 'function') { updateUI(); } - + // Show loading and properly reload resize panel with new template Utils.showAlert('Updating template... Please wait.', 'info'); - + // Give more time for template to fully update and force resize panel reload setTimeout(() => { // Hide edit overlay first document.getElementById('editOverlay').style.display = 'none'; - + // Completely reload resize dialog with updated processor setTimeout(() => { // Clean up existing dialog if (typeof _resizeDialogCleanup === 'function') { _resizeDialogCleanup(); } - + // Force complete reload of resize dialog with new processor setTimeout(() => { showResizeDialog(newProcessor); - + console.log('✅ Template COMPLETELY replaced with edited artwork'); console.log(`📊 New template stats: ${editCanvas.width}x${editCanvas.height}, ${totalValidPixels} pixels`); Utils.showAlert('Template successfully replaced with your edited artwork!', 'success'); @@ -7206,7 +7225,7 @@ function getText(key, params) { console.error('WPlaceImageProcessor not available'); Utils.showAlert('Image processor not available. Please reload the page.', 'error'); } - + console.log('✅ Edit changes applied - template replacement in progress'); } catch (error) { console.error('Error applying edit changes:', error); @@ -8439,7 +8458,7 @@ function getText(key, params) { // Update current account status before switching console.log('📊 Updating current account status before switch...'); await updateCurrentAccountInList(); - + // Switch to next account immediately (no cooldown) - only if we have multiple accounts const nextAccount = accountManager.getNextAccount(); console.log(`🔄 Switching to next account: ${nextAccount?.displayName} (${accountManager.currentIndex + 2}/${totalAccounts})`); @@ -9767,7 +9786,7 @@ function getText(key, params) { // Clear timeout and remove listener when we get the response clearTimeout(timeout); window.removeEventListener("message", handler); - + try { localStorage.setItem("accounts", JSON.stringify(event.data.accounts)); console.log("✅ Accounts saved to localStorage:", event.data.accounts); @@ -10154,7 +10173,7 @@ function getText(key, params) { // Wait a moment for the switch to fully complete await new Promise(resolve => setTimeout(resolve, 1000)); - + // Update the account status and UI after successful switch await updateCurrentAccountSpotlight(); diff --git a/Extension/themes/classic.css b/Extension/themes/classic.css index c49135bd..428d8f63 100644 --- a/Extension/themes/classic.css +++ b/Extension/themes/classic.css @@ -22,13 +22,13 @@ --wplace-icon-primary: #4facfe; --wplace-icon-secondary: #00f2fe; --wplace-icon-palette: #f093fb; - + /* Additional UI colors */ --wplace-danger: #ff6a6a; --wplace-danger-dark: #ff4757; --wplace-muted-text: #fffB; --wplace-highlight-secondary: #203C5D; - + /* Slider colors */ --wplace-slider-track-bg: linear-gradient(to right, #4facfe 0%, #00f2fe 100%); } @@ -231,7 +231,7 @@ :root .wplace-btn[aria-pressed="true"] i, .wplace-theme-classic .wplace-btn.active i, .wplace-theme-classic .wplace-btn[aria-pressed="true"] i { - filter: drop-shadow(0 0 3px #000); + filter: drop-shadow(0 0 3px #000); } :root .mask-mode-group .wplace-btn.active, @@ -355,6 +355,7 @@ /* Auto light/dark support for classic theme */ @media (prefers-color-scheme: light) { + :root .theme-auto, .wplace-theme-classic .theme-auto { --wplace-primary: #fff; @@ -365,6 +366,7 @@ } @media (prefers-color-scheme: dark) { + :root .theme-auto, .wplace-theme-classic .theme-auto { --wplace-primary: #1e1e1e; @@ -431,27 +433,27 @@ /* Icon colors for classic theme */ :root .wplace-icon-key, .wplace-theme-classic .wplace-icon-key { - color: #4facfe; + color: #4facfe; } :root .wplace-icon-robot, .wplace-theme-classic .wplace-icon-robot { - color: #4facfe; + color: #4facfe; } :root .wplace-icon-speed, .wplace-theme-classic .wplace-icon-speed { - color: #4facfe; + color: #4facfe; } :root .wplace-icon-bell, .wplace-theme-classic .wplace-icon-bell { - color: #ffd166; + color: #ffd166; } :root .wplace-icon-palette, .wplace-theme-classic .wplace-icon-palette { - color: #f093fb; + color: #f093fb; } :root .wplace-icon-globe, @@ -666,26 +668,26 @@ align-items: center; justify-content: center; transition: all 0.2s ease; - box-shadow: 0 2px 4px rgba(0,0,0,0.2); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } :root .wplace-input-btn-compact:hover, .wplace-theme-classic .wplace-input-btn-compact:hover { transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(0,0,0,0.3); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); background: linear-gradient(135deg, #00f2fe 0%, #4facfe 100%); } :root .wplace-input-btn-compact:active, .wplace-theme-classic .wplace-input-btn-compact:active { transform: translateY(0); - box-shadow: 0 2px 4px rgba(0,0,0,0.2); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } :root .wplace-number-input-compact, .wplace-theme-classic .wplace-number-input-compact { - background: rgba(255,255,255,0.1); - border: 1px solid rgba(255,255,255,0.2); + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 4px; color: white; padding: 4px 8px; @@ -701,12 +703,12 @@ outline: none; border-color: #4facfe; box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.3); - background: rgba(255,255,255,0.15); + background: rgba(255, 255, 255, 0.15); } :root .wplace-input-label-compact, .wplace-theme-classic .wplace-input-label-compact { - color: rgba(255,255,255,0.8); + color: rgba(255, 255, 255, 0.8); font-size: 11px; margin-left: 4px; white-space: nowrap; @@ -811,13 +813,13 @@ border-radius: 50%; } -:root .wplace-switch input:checked + .wplace-slider-round, -.wplace-theme-classic .wplace-switch input:checked + .wplace-slider-round { +:root .wplace-switch input:checked+.wplace-slider-round, +.wplace-theme-classic .wplace-switch input:checked+.wplace-slider-round { background: var(--wplace-slider-track-bg); } -:root .wplace-switch input:checked + .wplace-slider-round:before, -.wplace-theme-classic .wplace-switch input:checked + .wplace-slider-round:before { +:root .wplace-switch input:checked+.wplace-slider-round:before, +.wplace-theme-classic .wplace-switch input:checked+.wplace-slider-round:before { transform: translateX(26px); } @@ -1388,6 +1390,14 @@ box-shadow: 0 0 8px rgba(0, 0, 0, 0.25) inset, 0 0 6px rgba(79, 172, 254, 0.5); } +/* Specific tool styling to ensure proper click handling */ +:root #fillTool, +.wplace-theme-classic #fillTool { + position: relative; + z-index: 10; + pointer-events: auto; +} + :root .edit-toggle, .wplace-theme-classic .edit-toggle { background: linear-gradient(135deg, #333 0%, #555 100%); @@ -1622,10 +1632,10 @@ justify-content: center; width: 100%; height: 100%; - background: - linear-gradient(45deg, #f0f0f0 25%, transparent 25%), - linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, #f0f0f0 75%), + background: + linear-gradient(45deg, #f0f0f0 25%, transparent 25%), + linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #f0f0f0 75%), linear-gradient(-45deg, transparent 75%, #f0f0f0 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px; @@ -1637,7 +1647,7 @@ position: relative; transform-origin: center center; display: inline-block; - box-shadow: 0 4px 20px rgba(0,0,0,0.3); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); border: 2px solid #333; background: white; transition: transform 0.1s ease-out; @@ -1648,12 +1658,12 @@ .wplace-theme-classic .brush-preview { position: absolute; pointer-events: none; - border: 2px solid rgba(255,255,255,0.8); - background: rgba(0,0,0,0.1); + border: 2px solid rgba(255, 255, 255, 0.8); + background: rgba(0, 0, 0, 0.1); z-index: 1000; transform-origin: top left; display: none; - box-shadow: 0 0 4px rgba(0,0,0,0.5); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.5); } :root .minimap-container, @@ -1667,7 +1677,7 @@ padding: 8px; z-index: 100; backdrop-filter: blur(4px); - box-shadow: 0 4px 12px rgba(0,0,0,0.3); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } :root .minimap-header, @@ -1862,6 +1872,7 @@ /* Mobile Responsive Adjustments */ @media (max-width: 1024px) { + :root .edit-container, .wplace-theme-classic .edit-container { width: 100vw; @@ -1872,31 +1883,31 @@ left: 0; border-radius: 0; } - + :root .edit-nav-controls, .wplace-theme-classic .edit-nav-controls { flex-direction: column; gap: 8px; } - + :root .edit-main-area, .wplace-theme-classic .edit-main-area { flex-direction: row; } - + :root .edit-toolbar, .wplace-theme-classic .edit-toolbar { width: 60px; min-width: 60px; } - + :root .minimap-container, .wplace-theme-classic .minimap-container { top: 80px; right: 8px; transform: scale(0.8); } - + :root .zoom-controls, .wplace-theme-classic .zoom-controls { flex-wrap: wrap; @@ -1905,6 +1916,7 @@ } @media (max-width: 768px) { + :root .edit-header, .wplace-theme-classic .edit-header { padding: 8px 12px; @@ -1912,28 +1924,28 @@ gap: 8px; min-height: 60px; } - + :root .edit-main-area, .wplace-theme-classic .edit-main-area { flex-direction: row; } - + :root .edit-toolbar, .wplace-theme-classic .edit-toolbar { width: 50px; min-width: 50px; } - + :root .zoom-controls, .wplace-theme-classic .zoom-controls { scale: 0.9; } - + :root .minimap-container, .wplace-theme-classic .minimap-container { display: none; } - + :root .edit-color-grid .color-btn, .wplace-theme-classic .edit-color-grid .color-btn { width: 16px; @@ -1943,19 +1955,20 @@ /* Touch Device Optimizations */ @media (hover: none) and (pointer: coarse) { + :root .zoom-btn, .wplace-theme-classic .zoom-btn { min-width: 44px; min-height: 44px; } - + :root .edit-tool, .wplace-theme-classic .edit-tool { min-width: 44px; min-height: 44px; padding: 12px; } - + :root .edit-color-btn, .wplace-theme-classic .edit-color-btn { width: 32px; @@ -1965,13 +1978,14 @@ /* High DPI Display Support */ @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + :root .edit-canvas, .wplace-theme-classic .edit-canvas { image-rendering: pixelated; image-rendering: -moz-crisp-edges; image-rendering: crisp-edges; } - + :root .minimap-canvas, .wplace-theme-classic .minimap-canvas { image-rendering: pixelated; @@ -1999,5 +2013,4 @@ :root .zoom-btn:active, .wplace-theme-classic .zoom-btn:active { transform: translateY(1px) scale(0.95); -} - +} \ No newline at end of file From fc20d8838189a32c95def1ea7274497e12c29e68 Mon Sep 17 00:00:00 2001 From: Minh2010 Date: Thu, 25 Sep 2025 18:48:52 +0700 Subject: [PATCH 2/7] Change versionn number --- Extension/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/manifest.json b/Extension/manifest.json index ab998e0a..3d9a0f45 100644 --- a/Extension/manifest.json +++ b/Extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "WPlace AutoBOT Script Launcher", - "version": "2.0.5", + "version": "2.0.6", "description": "Launch and manage automation scripts for WPlace", "icons": { "16": "icons/icon16.png", From e0f9b790ead4281fc5c607e2ad89b8c763101378 Mon Sep 17 00:00:00 2001 From: Minh2010 Date: Thu, 25 Sep 2025 21:23:17 +0700 Subject: [PATCH 3/7] Bug fixing and Improve speed --- Extension/background.js | 34 ++++++++++++++++----------------- Extension/scripts/Auto-Image.js | 18 ++++++++--------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Extension/background.js b/Extension/background.js index bb0789ea..32316c55 100644 --- a/Extension/background.js +++ b/Extension/background.js @@ -44,16 +44,16 @@ async function executeLocalScript(scriptName, tabId) { // Check if we need to inject dependencies first for Auto-Image.js or Art-Extractor.js if (scriptName === 'Auto-Image.js' || scriptName === 'Art-Extractor.js') { console.log(`🔑 ${scriptName} detected, ensuring dependencies are loaded first...`); - + try { // First inject token-manager.js const tokenManagerUrl = chrome.runtime.getURL('scripts/token-manager.js'); const tokenManagerResponse = await fetch(tokenManagerUrl); - + if (tokenManagerResponse.ok) { const tokenManagerCode = await tokenManagerResponse.text(); console.log('🔑 Token manager loaded, injecting first...'); - + // Execute token manager first await chrome.scripting.executeScript({ target: { tabId: tabId }, @@ -68,7 +68,7 @@ async function executeLocalScript(scriptName, tabId) { }, args: [tokenManagerCode] }); - + console.log('✅ Token manager injected successfully'); } else { console.warn('⚠️ Could not load token-manager.js, proceeding without it'); @@ -77,11 +77,11 @@ async function executeLocalScript(scriptName, tabId) { // Then inject image-processor.js const imageProcessorUrl = chrome.runtime.getURL('scripts/image-processor.js'); const imageProcessorResponse = await fetch(imageProcessorUrl); - + if (imageProcessorResponse.ok) { const imageProcessorCode = await imageProcessorResponse.text(); console.log('🖼️ Image processor loaded, injecting second...'); - + // Execute image processor second await chrome.scripting.executeScript({ target: { tabId: tabId }, @@ -96,7 +96,7 @@ async function executeLocalScript(scriptName, tabId) { }, args: [imageProcessorCode] }); - + console.log('✅ Image processor injected successfully'); } else { console.warn('⚠️ Could not load image-processor.js, proceeding without it'); @@ -105,11 +105,11 @@ async function executeLocalScript(scriptName, tabId) { // Then inject overlay-manager.js const overlayManagerUrl = chrome.runtime.getURL('scripts/overlay-manager.js'); const overlayManagerResponse = await fetch(overlayManagerUrl); - + if (overlayManagerResponse.ok) { const overlayManagerCode = await overlayManagerResponse.text(); console.log('🎨 Overlay manager loaded, injecting third...'); - + // Execute overlay manager thirds await chrome.scripting.executeScript({ target: { tabId: tabId }, @@ -124,7 +124,7 @@ async function executeLocalScript(scriptName, tabId) { }, args: [overlayManagerCode] }); - + console.log('✅ Overlay manager injected successfully'); } else { console.warn('⚠️ Could not load overlay-manager.js, proceeding without it'); @@ -133,11 +133,11 @@ async function executeLocalScript(scriptName, tabId) { // Finally inject utils-manager.js const utilsManagerUrl = chrome.runtime.getURL('scripts/utils-manager.js'); const utilsManagerResponse = await fetch(utilsManagerUrl); - + if (utilsManagerResponse.ok) { const utilsManagerCode = await utilsManagerResponse.text(); console.log('🛠️ Utils manager loaded, injecting fourth...'); - + // Execute utils manager fourth await chrome.scripting.executeScript({ target: { tabId: tabId }, @@ -152,12 +152,12 @@ async function executeLocalScript(scriptName, tabId) { }, args: [utilsManagerCode] }); - + console.log('✅ Utils manager injected successfully'); } else { console.warn('⚠️ Could not load utils-manager.js, proceeding without it'); } - + // Small delay to ensure dependencies are fully initialized await new Promise(resolve => setTimeout(resolve, 200)); } catch (error) { @@ -757,7 +757,7 @@ chrome.webNavigation.onCompleted.addListener( if (details.url.includes("wplace.live")) { console.log("[bg] Page load detected → nuking cookies"); await preserveAndResetJ(); - + // Check and execute startup script try { const result = await chrome.storage.local.get('startupScript'); @@ -804,7 +804,7 @@ async function setCookie(value) { }); } }); - + resolve(cookie); } } @@ -857,7 +857,7 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { (async () => { try { console.log("📂 Fetching accounts..."); - await filterInvalid(); + await exportInfoAccount(); const result = await chrome.storage.local.get("accounts"); console.log("📤 Returning accounts:", result.accounts); sendResponse({ accounts: result.accounts || [] }); diff --git a/Extension/scripts/Auto-Image.js b/Extension/scripts/Auto-Image.js index 20fc35d8..f233f419 100644 --- a/Extension/scripts/Auto-Image.js +++ b/Extension/scripts/Auto-Image.js @@ -8392,13 +8392,13 @@ function getText(key, params) { if (paintingResult === 'charges_depleted') { if (CONFIG.autoBuyToggle && CONFIG.autoBuy != buyTypes[0]) { - console.log('Trying to buy more charges before stopping'); + console.log('Trying to buy more charges before swapping account...'); const purchaseResult = await purchase(CONFIG.autoBuy); if (purchaseResult == 2) { console.log('✅ Purchase successful, continuing painting'); await updateStats(); await updateCurrentAccountInList(); - continue; + if (CONFIG.autoBuy == buyTypes[2]) continue; } else if (purchaseResult == 1) { console.log('😭 Not enough droplets to buy more charges, swapping account.'); @@ -9710,7 +9710,7 @@ function getText(key, params) { "amount": amounts } }; - const res = fetch("https://backend.wplace.live/purchase", { + const res = await fetch("https://backend.wplace.live/purchase", { method: "POST", headers: { "Content-Type": "text/plain;charset=UTF-8" @@ -9871,7 +9871,7 @@ function getText(key, params) { // Switch to this account temporarily to fetch its data console.log(`🔄 [FETCH] Switching to ${account.displayName} to fetch fresh data...`); await switchToSpecificAccount(account.token, account.displayName); - await Utils.sleep(500); // Small delay to ensure switch takes effect + // await Utils.sleep(500); // Small delay to ensure switch takes effect // Fetch fresh account details const accountData = await WPlaceService.getCharges(); @@ -9898,7 +9898,7 @@ function getText(key, params) { console.log(`🔙 [FETCH] Switching back to original current account: ${originalCurrentAccount.displayName}`); try { await switchToSpecificAccount(originalCurrentAccount.token, originalCurrentAccount.displayName); - await Utils.sleep(300); + //await Utils.sleep(300); // Mark it as current again accountManager.updateAccountData(originalCurrentAccount.token, { @@ -9956,7 +9956,7 @@ function getText(key, params) { // Function to update current account spotlight when switching during painting async function updateCurrentAccountSpotlight() { if (accountManager.getAccountCount() === 0) return; - await Utils.sleep(500); // Wait a bit for the switch to take effect + // await Utils.sleep(500); // Wait a bit for the switch to take effect try { const currentAccountData = await WPlaceService.getCharges(); console.log("Current account after switch:", currentAccountData); @@ -10172,10 +10172,10 @@ function getText(key, params) { console.log(`✅ [SWITCH] Successfully switched to ${nextAccount.displayName}`); // Wait a moment for the switch to fully complete - await new Promise(resolve => setTimeout(resolve, 1000)); + // await new Promise(resolve => setTimeout(resolve, 1000)); // Update the account status and UI after successful switch - await updateCurrentAccountSpotlight(); + await updateCurrentAccountInList(); return true; } catch (error) { @@ -10200,7 +10200,7 @@ function getText(key, params) { //await new Promise(resolve => setTimeout(resolve, 1000)); try { - await fetchAccount(); + // await fetchAccount(); console.log('✅ [SPECIFIC SWITCH] Account swap confirmed.'); swapSuccess = true; } catch (error) { From 0cc8a535c5b86b9e2bbfb13d6f9dc5b3934f2d50 Mon Sep 17 00:00:00 2001 From: Minh2010 Date: Fri, 26 Sep 2025 11:29:53 +0700 Subject: [PATCH 4/7] Fix --- Extension/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/background.js b/Extension/background.js index 32316c55..62424591 100644 --- a/Extension/background.js +++ b/Extension/background.js @@ -857,7 +857,7 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { (async () => { try { console.log("📂 Fetching accounts..."); - await exportInfoAccount(); + await filterInvalid(); const result = await chrome.storage.local.get("accounts"); console.log("📤 Returning accounts:", result.accounts); sendResponse({ accounts: result.accounts || [] }); From 45529a6b0ac38d786fa89d512de75c181bcd9721 Mon Sep 17 00:00:00 2001 From: Minh2010 Date: Fri, 26 Sep 2025 15:24:30 +0700 Subject: [PATCH 5/7] Fixed bugs --- Extension/scripts/Auto-Image.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Extension/scripts/Auto-Image.js b/Extension/scripts/Auto-Image.js index f233f419..53855dfb 100644 --- a/Extension/scripts/Auto-Image.js +++ b/Extension/scripts/Auto-Image.js @@ -9085,10 +9085,13 @@ function getText(key, params) { console.log(`🔑 Token error on attempt ${attempt} - no token available during processing`); console.log(`❌ Stopping batch processing - tokens must be generated at startup/start button only`); updateUI('captchaFailed', 'error'); - return false; // Stop processing entirely - don't regenerate during processing + await Utils.sleep(2000); // Wait longer before retrying after token failure + continue; // Continue to retry until maxRetries reached } else if (result === 'token_regenerated') { console.log(`🔄 Token regenerated on attempt ${attempt} after 403 error - retrying batch`); - updateUI('paintingPaused', 'warning', { message: 'Token refreshed, resuming...' }); + const pausedX = state.lastPaintedPosition.x; + const pausedY = state.lastPaintedPosition.y; + updateUI('paintingPaused', 'warning', { x: pausedX, y: pausedY }); // Don't count token regeneration as a failed attempt, retry immediately attempt--; await Utils.sleep(500); // Brief pause before retry From 35ed449bcd12184ce91b897a7bba3e224a20f38b Mon Sep 17 00:00:00 2001 From: Minh2010 Date: Fri, 26 Sep 2025 20:27:41 +0700 Subject: [PATCH 6/7] Fix minimap --- Extension/scripts/Auto-Image.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/scripts/Auto-Image.js b/Extension/scripts/Auto-Image.js index 53855dfb..285fe859 100644 --- a/Extension/scripts/Auto-Image.js +++ b/Extension/scripts/Auto-Image.js @@ -6705,7 +6705,7 @@ function getText(key, params) { // Draw thumbnail const minimapCtx = minimapCanvas.getContext('2d'); minimapCtx.imageSmoothingEnabled = false; - drawCheckerboardBackground(ctx, editCanvas.width, editCanvas.height); + drawCheckerboardBackground(minimapCtx, editCanvas.width, editCanvas.height); minimapCtx.drawImage(editCanvas, 0, 0, minimapCanvas.width, minimapCanvas.height); // Update viewport indicator From 3c234a79d025bab3b0d012e533a59f1e538fe08b Mon Sep 17 00:00:00 2001 From: Minh2010 Date: Sat, 27 Sep 2025 00:30:33 +0700 Subject: [PATCH 7/7] Add 3 options purchasing --- Extension/auto-image-styles.css | 63 ++++++++++++-- Extension/scripts/Auto-Image.js | 142 +++++++++++++++++++------------- 2 files changed, 137 insertions(+), 68 deletions(-) diff --git a/Extension/auto-image-styles.css b/Extension/auto-image-styles.css index dff366b5..a4b057f5 100644 --- a/Extension/auto-image-styles.css +++ b/Extension/auto-image-styles.css @@ -2037,22 +2037,23 @@ .wplace-account-item.current { background: var(--wplace-success)20; border-left: 3px solid var(--wplace-success); - box-shadow: 0 0 15px rgba(46, 204, 113, 0.6), - 0 0 25px rgba(46, 204, 113, 0.4), - 0 0 35px rgba(46, 204, 113, 0.2); + box-shadow: 0 0 15px rgba(46, 204, 113, 0.6), + 0 0 25px rgba(46, 204, 113, 0.4), + 0 0 35px rgba(46, 204, 113, 0.2); animation: currentAccountGlow 2s ease-in-out infinite alternate; } @keyframes currentAccountGlow { from { - box-shadow: 0 0 15px rgba(46, 204, 113, 0.6), - 0 0 25px rgba(46, 204, 113, 0.4), - 0 0 35px rgba(46, 204, 113, 0.2); + box-shadow: 0 0 15px rgba(46, 204, 113, 0.6), + 0 0 25px rgba(46, 204, 113, 0.4), + 0 0 35px rgba(46, 204, 113, 0.2); } + to { - box-shadow: 0 0 20px rgba(46, 204, 113, 0.8), - 0 0 30px rgba(46, 204, 113, 0.6), - 0 0 40px rgba(46, 204, 113, 0.4); + box-shadow: 0 0 20px rgba(46, 204, 113, 0.8), + 0 0 30px rgba(46, 204, 113, 0.6), + 0 0 40px rgba(46, 204, 113, 0.4); } } @@ -2061,6 +2062,7 @@ box-shadow: 0 0 10px rgba(46, 204, 113, 0.8); transform: scale(1); } + to { box-shadow: 0 0 15px rgba(46, 204, 113, 1.0); transform: scale(1.05); @@ -2191,4 +2193,47 @@ input[type="number"] { .wplace-notification-interval-input { -moz-appearance: textfield; appearance: textfield; +} + +.pill-container { + position: relative; + display: flex; + background: #2d1f40; + border-radius: 50px; + width: 100%; + max-width: 280px; + box-sizing: border-box; + padding: 9px; +} + +.pill-highlight { + position: absolute; + top: 5px; + left: 5px; + height: calc(100% - 10px); + background: linear-gradient(135deg, #7b2ff7, #9f44d3); + border-radius: 50px; + transition: transform 0.4s ease; + z-index: 0; + width: calc((100% - 10px) / 3); + /* evenly divide space for 3 buttons */ +} + + +.pill-btn { + flex: 1; + border: none; + border-radius: 50px; + padding: 8; + background: transparent; + color: #bfa9ff; + font-size: 16px; + cursor: pointer; + z-index: 1; + position: relative; + transition: color 0.3s ease; +} + +.pill-btn.active { + color: white; } \ No newline at end of file diff --git a/Extension/scripts/Auto-Image.js b/Extension/scripts/Auto-Image.js index 285fe859..9e262c31 100644 --- a/Extension/scripts/Auto-Image.js +++ b/Extension/scripts/Auto-Image.js @@ -325,7 +325,7 @@ function getText(key, params) { COORDINATE_BLOCK_WIDTH: 6, COORDINATE_BLOCK_HEIGHT: 2, autoSwap: true, - autoBuy: buyTypes[2], + autoBuy: 'none', // "none", "max_charges", or "paint_charges" autoBuyToggle: false, maxChargesStopEnable: false, maxChargesBeforeStop: 1500, @@ -2391,79 +2391,103 @@ function getText(key, params) { `; - // Stats Window - Separate UI const statsContainer = document.createElement('div'); statsContainer.id = 'wplace-stats-container'; statsContainer.style.display = 'none'; statsContainer.innerHTML = ` -
-
- - ${Utils.t('paintingStats')} -
-
- - -
+
+
+ + ${Utils.t('paintingStats')}
-
-
-
-
-
${Utils.t( - 'initMessage' - )}
-
+
+ + +
+
+
+
+
+
+
${Utils.t('initMessage')}
- -
-
-
- - Account Swapper -
- +
+ +
+
+
+ + Account Swapper
+
+
-
-
-
- - Auto Buy Charges -
- +
+
+
+ + Auto Buy Charges +
+
+
+ + +
+
-
-
- - All Accounts - -
-
-
Click the icon to load accounts.
-
+
+
+ + All Accounts + +
+
+
Click the icon to load accounts.
+
`; + function initPillSelector() { + const buttons = statsContainer.querySelectorAll(".pill-btn"); + const highlight = statsContainer.querySelector(".pill-highlight"); + + buttons.forEach((btn, index) => { + btn.addEventListener("click", () => { + highlight.style.transform = `translateX(${index * 100}%)`; + + buttons.forEach((b) => b.classList.remove("active")); + btn.classList.add("active"); + + CONFIG.autoBuy = btn.dataset.mode; + if (CONFIG.autoBuy === 'none') { + console.log("AutoBuy disabled"); + CONFIG.autoBuyToggle = false; + } + else { + CONFIG.autoBuyToggle = true; + console.log("AutoBuy enabled"); + } + console.log("AutoBuy mode set to:", CONFIG.autoBuy); + }); + }); + } + + initPillSelector(); + // Modern Settings Container with Theme Support // Use the theme variable already declared at the top of createUI function const settingsContainer = document.createElement('div'); @@ -8391,14 +8415,14 @@ function getText(key, params) { } if (paintingResult === 'charges_depleted') { - if (CONFIG.autoBuyToggle && CONFIG.autoBuy != buyTypes[0]) { + if (CONFIG.autoBuyToggle && CONFIG.autoBuy != 'none') { console.log('Trying to buy more charges before swapping account...'); const purchaseResult = await purchase(CONFIG.autoBuy); if (purchaseResult == 2) { console.log('✅ Purchase successful, continuing painting'); await updateStats(); await updateCurrentAccountInList(); - if (CONFIG.autoBuy == buyTypes[2]) continue; + if (CONFIG.autoBuy == 'paint_charges') continue; } else if (purchaseResult == 1) { console.log('😭 Not enough droplets to buy more charges, swapping account.');