From c6e292a0dc3c40151b717412ebc6227e747ec111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=A9ng?= <99890979+maiminhdung@users.noreply.github.com> Date: Thu, 14 Aug 2025 17:57:02 +0700 Subject: [PATCH] Add Import/Export data file + New UI --- Auto-Image.js | 1201 +++++++++++++++++++++++++++++++++++---------- FR.md | 1 + PT.md | 1 + test-save-load.md | 38 -- 4 files changed, 949 insertions(+), 292 deletions(-) delete mode 100644 test-save-load.md diff --git a/Auto-Image.js b/Auto-Image.js index 925c2ae..20cee08 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -57,13 +57,20 @@ captchaNeeded: "❗ Token CAPTCHA necessário. Pinte um pixel manualmente para continuar.", saveData: "Salvar Progresso", - loadData: "Carregar Progresso", + loadData: "Carregar Progresso", + saveToFile: "Salvar em Arquivo", + loadFromFile: "Carregar de Arquivo", + dataManager: "Dados", autoSaved: "✅ Progresso salvo automaticamente", dataLoaded: "✅ Progresso carregado com sucesso", + fileSaved: "✅ Salvo em arquivo com sucesso", + fileLoaded: "✅ Carregado de arquivo com sucesso", noSavedData: "❌ Nenhum progresso salvo encontrado", savedDataFound: "✅ Progresso salvo encontrado! Carregar para continuar?", savedDate: "Salvo em: {date}", clickLoadToContinue: "Clique em 'Carregar Progresso' para continuar.", + fileError: "❌ Erro ao processar arquivo", + invalidFileFormat: "❌ Formato de arquivo inválido", }, en: { title: "WPlace Auto-Image", @@ -103,12 +110,19 @@ "❗ CAPTCHA token needed. Paint one pixel manually to continue.", saveData: "Save Progress", loadData: "Load Progress", + saveToFile: "Save to File", + loadFromFile: "Load from File", + dataManager: "Data Manager", autoSaved: "✅ Progress saved automatically", dataLoaded: "✅ Progress loaded successfully", + fileSaved: "✅ Progress saved to file successfully", + fileLoaded: "✅ Progress loaded from file successfully", noSavedData: "❌ No saved progress found", savedDataFound: "✅ Saved progress found! Load to continue?", savedDate: "Saved on: {date}", clickLoadToContinue: "Click 'Load Progress' to continue.", + fileError: "❌ Error processing file", + invalidFileFormat: "❌ Invalid file format", }, }; @@ -152,6 +166,7 @@ console.log("✅ CAPTCHA Token Captured:", payload.t); // Store the token for our bot to use. capturedCaptchaToken = payload.t; + // Notify the user that the token is captured and they can start the bot. if ( document @@ -209,6 +224,44 @@ input.click(); }), + createFileDownloader: (data, filename) => { + const blob = new Blob([data], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }, + + createFileUploader: () => + new Promise((resolve, reject) => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".json"; + input.onchange = (e) => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = () => { + try { + const data = JSON.parse(reader.result); + resolve(data); + } catch (error) { + reject(new Error('Invalid JSON file')); + } + }; + reader.onerror = () => reject(new Error('File reading error')); + reader.readAsText(file); + } else { + reject(new Error('No file selected')); + } + }; + input.click(); + }), + extractAvailableColors: () => { const colorElements = document.querySelectorAll('[id^="color-"]'); return Array.from(colorElements) @@ -270,6 +323,38 @@ } }, + saveProgressToFile: () => { + try { + const progressData = { + timestamp: Date.now(), + version: "1.0", + appName: "WPlace Auto-Image", + state: { + imageLoaded: state.imageLoaded, + totalPixels: state.totalPixels, + paintedPixels: state.paintedPixels, + lastPosition: state.lastPosition, + startPosition: state.startPosition, + region: state.region, + paintedMap: state.paintedMap, + imageData: state.imageData, + availableColors: state.availableColors, + language: state.language + } + }; + + const filename = `wplace-progress-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`; + const dataString = JSON.stringify(progressData, null, 2); + Utils.createFileDownloader(dataString, filename); + + console.log('✅ Progress saved to file'); + return true; + } catch (error) { + console.error('❌ Error saving progress to file:', error); + return false; + } + }, + loadProgress: () => { try { const savedData = localStorage.getItem('wplace-auto-image-progress'); @@ -361,6 +446,7 @@ // Update stats to show current progress updateStats(); + updateDataButtons(); return true; } catch (error) { console.error('❌ Error restoring progress:', error); @@ -383,6 +469,132 @@ return localStorage.getItem('wplace-auto-image-progress') !== null; }, + saveProgressToFile: () => { + try { + updateUI("default", "default", {}); + + const progressData = { + timestamp: Date.now(), + version: "1.0", + appName: "WPlace Auto-Image", + state: { + imageLoaded: state.imageLoaded, + totalPixels: state.totalPixels, + paintedPixels: state.paintedPixels, + lastPosition: state.lastPosition, + startPosition: state.startPosition, + region: state.region, + paintedMap: state.paintedMap, + imageData: state.imageData, + availableColors: state.availableColors, + language: state.language + } + }; + + const filename = `wplace-progress-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`; + const dataString = JSON.stringify(progressData, null, 2); + Utils.createFileDownloader(dataString, filename); + + console.log('✅ Progress saved to file'); + return true; + } catch (error) { + console.error('❌ Error saving progress to file:', error); + return false; + } + }, + + loadProgressFromFile: async () => { + try { + updateUI("default", "default", {}); + + const fileData = await Utils.createFileUploader(); + + // Validate file format + if (!fileData.state || !fileData.timestamp) { + throw new Error('Invalid file format - missing required fields'); + } + + if (!fileData.appName || fileData.appName !== "WPlace Auto-Image") { + throw new Error('Invalid file format - not a WPlace Auto-Image file'); + } + + const savedState = fileData.state; + + // Restore state + state.imageLoaded = savedState.imageLoaded; + state.totalPixels = savedState.totalPixels; + state.paintedPixels = savedState.paintedPixels; + state.lastPosition = savedState.lastPosition || { x: 0, y: 0 }; + state.startPosition = savedState.startPosition; + state.region = savedState.region; + state.paintedMap = savedState.paintedMap; + state.imageData = savedState.imageData; + state.availableColors = savedState.availableColors; + state.language = savedState.language; + state.colorsChecked = savedState.availableColors && savedState.availableColors.length > 0; + + // Update UI to reflect restored state + if (state.imageLoaded) { + const initBotBtn = document.querySelector("#initBotBtn"); + const uploadBtn = document.querySelector("#uploadBtn"); + const resizeBtn = document.querySelector("#resizeBtn"); + const selectPosBtn = document.querySelector("#selectPosBtn"); + const startBtn = document.querySelector("#startBtn"); + const saveBtn = document.querySelector("#saveBtn"); + const progressBar = document.querySelector("#progressBar"); + + // Show/hide appropriate buttons based on state + if (state.colorsChecked) { + initBotBtn.style.display = "none"; + uploadBtn.disabled = false; + selectPosBtn.disabled = false; + } else { + initBotBtn.style.display = "block"; + uploadBtn.disabled = true; + selectPosBtn.disabled = true; + } + + resizeBtn.disabled = false; + saveBtn.disabled = false; + + if (state.startPosition && state.region) { + selectPosBtn.disabled = false; + startBtn.disabled = false; + } + + // Update progress bar + const progress = state.totalPixels > 0 ? Math.round((state.paintedPixels / state.totalPixels) * 100) : 0; + progressBar.style.width = `${progress}%`; + + // Update status message based on progress + if (state.paintedPixels > 0) { + if (state.lastPosition.x > 0 || state.lastPosition.y > 0) { + updateUI("paintingPaused", "warning", { + x: state.lastPosition.x, + y: state.lastPosition.y + }); + } else { + updateUI("paintingProgress", "default", { + painted: state.paintedPixels, + total: state.totalPixels, + }); + } + } else { + updateUI("imageLoaded", "success", { count: state.totalPixels }); + } + } + + // Update stats to show current progress + updateStats(); + updateDataButtons(); + console.log('✅ Progress loaded from file'); + return true; + } catch (error) { + console.error('❌ Error loading progress from file:', error); + return false; + } + }, + showAlert: (message, type = "info") => { const alert = document.createElement("div"); alert.style.position = "fixed"; @@ -583,143 +795,332 @@ 100% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0); } } @keyframes slideIn { - from { transform: translateY(20px); opacity: 0; } + from { transform: translateY(-10px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } #wplace-image-bot-container { position: fixed; top: 20px; right: 20px; - width: 300px; - background: ${CONFIG.THEME.primary}; + width: 280px; + max-height: calc(100vh - 40px); + background: linear-gradient(135deg, ${CONFIG.THEME.primary} 0%, #1a1a1a 100%); border: 1px solid ${CONFIG.THEME.accent}; - border-radius: 8px; + border-radius: 12px; padding: 0; - box-shadow: 0 5px 15px rgba(0,0,0,0.5); + box-shadow: 0 8px 32px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.1); z-index: 9998; font-family: 'Segoe UI', Roboto, sans-serif; color: ${CONFIG.THEME.text}; animation: slideIn 0.4s ease-out; overflow: hidden; + backdrop-filter: blur(10px); + transition: all 0.3s ease; + user-select: none; + } + #wplace-image-bot-container.wplace-dragging { + transition: none; + box-shadow: 0 12px 40px rgba(0,0,0,0.8), 0 0 0 2px rgba(255,255,255,0.2); + transform: scale(1.02); + z-index: 9999; + } + #wplace-image-bot-container.wplace-minimized { + width: 200px; + height: auto; + } + #wplace-image-bot-container.wplace-compact { + width: 240px; + } + #wplace-image-bot-container.wplace-compact .wplace-content { + padding: 12px; + } + #wplace-image-bot-container.wplace-compact .wplace-tab { + padding: 8px 10px; + font-size: 10px; + } + #wplace-image-bot-container.wplace-compact .wplace-btn { + padding: 6px 12px; + font-size: 11px; + gap: 4px; + } + #wplace-image-bot-container.wplace-compact .wplace-stats { + padding: 8px; + } + #wplace-image-bot-container.wplace-compact .wplace-stat-item { + font-size: 11px; + padding: 4px 0; + } + + /* Stats Container */ + #wplace-stats-container { + position: fixed; + top: 20px; + left: 20px; + width: 280px; + max-height: calc(100vh - 40px); + background: linear-gradient(135deg, ${CONFIG.THEME.primary} 0%, #1a1a1a 100%); + border: 1px solid ${CONFIG.THEME.accent}; + border-radius: 12px; + padding: 0; + box-shadow: 0 8px 32px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.1); + z-index: 9997; + font-family: 'Segoe UI', Roboto, sans-serif; + color: ${CONFIG.THEME.text}; + animation: slideIn 0.4s ease-out; + overflow: hidden; + backdrop-filter: blur(10px); + transition: all 0.3s ease; + user-select: none; + } + #wplace-stats-container.wplace-dragging { + transition: none; + box-shadow: 0 12px 40px rgba(0,0,0,0.8), 0 0 0 2px rgba(255,255,255,0.2); + transform: scale(1.02); + z-index: 9999; } .wplace-header { - padding: 12px 15px; - background: ${CONFIG.THEME.secondary}; + padding: 8px 12px; + background: linear-gradient(135deg, ${CONFIG.THEME.secondary} 0%, #2a2a2a 100%); color: ${CONFIG.THEME.highlight}; - font-size: 16px; - font-weight: 600; + font-size: 13px; + font-weight: 700; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; + border-bottom: 1px solid rgba(255,255,255,0.1); + text-shadow: 0 1px 2px rgba(0,0,0,0.5); + transition: background 0.2s ease; + } + .wplace-header:hover { + background: linear-gradient(135deg, ${CONFIG.THEME.secondary} 10%, #3a3a3a 100%); + } + .wplace-header:active { + background: linear-gradient(135deg, ${CONFIG.THEME.secondary} 20%, #4a4a4a 100%); + cursor: grabbing; } .wplace-header-title { display: flex; align-items: center; - gap: 8px; + gap: 6px; } .wplace-header-controls { display: flex; - gap: 10px; + gap: 6px; } .wplace-header-btn { - background: none; + background: rgba(255,255,255,0.1); border: none; color: ${CONFIG.THEME.text}; cursor: pointer; - opacity: 0.7; - transition: opacity 0.2s; + border-radius: 4px; + width: 18px; + height: 18px; + font-size: 10px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; } .wplace-header-btn:hover { - opacity: 1; + background: ${CONFIG.THEME.accent}; + transform: scale(1.1); } .wplace-content { - padding: 15px; + padding: 12px; display: block; } + .wplace-content.wplace-hidden { + display: none; + } + .wplace-status-section { + margin-bottom: 12px; + padding: 8px; + background: rgba(255,255,255,0.03); + border-radius: 6px; + border: 1px solid rgba(255,255,255,0.1); + } + .wplace-section { + margin-bottom: 12px; + padding: 12px; + background: rgba(255,255,255,0.03); + border-radius: 6px; + border: 1px solid rgba(255,255,255,0.1); + } + .wplace-section-title { + font-size: 11px; + font-weight: 600; + margin-bottom: 8px; + color: ${CONFIG.THEME.highlight}; + display: flex; + align-items: center; + gap: 6px; + text-transform: uppercase; + letter-spacing: 0.5px; + } + .wplace-section { + margin-bottom: 12px; + padding: 12px; + background: rgba(255,255,255,0.03); + border-radius: 6px; + border: 1px solid rgba(255,255,255,0.1); + } + .wplace-section-title { + font-size: 11px; + font-weight: 600; + margin-bottom: 8px; + color: ${CONFIG.THEME.highlight}; + display: flex; + align-items: center; + gap: 6px; + text-transform: uppercase; + letter-spacing: 0.5px; + } .wplace-controls { display: flex; flex-direction: column; - gap: 10px; - margin-bottom: 15px; + gap: 8px; + } + .wplace-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + } + .wplace-row.single { + grid-template-columns: 1fr; } .wplace-btn { - padding: 10px; + padding: 8px 12px; border: none; border-radius: 6px; - font-weight: 600; + font-weight: 500; cursor: pointer; display: flex; align-items: center; justify-content: center; - gap: 8px; - transition: all 0.2s; + gap: 6px; + font-size: 11px; + transition: all 0.3s ease; + position: relative; + overflow: hidden; + background: linear-gradient(135deg, ${CONFIG.THEME.accent} 0%, #4a4a4a 100%); + border: 1px solid rgba(255,255,255,0.1); } - .wplace-btn:hover { - transform: translateY(-2px); + .wplace-btn::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent); + transition: left 0.5s ease; + } + .wplace-btn:hover:not(:disabled)::before { + left: 100%; + } + .wplace-btn:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0,0,0,0.4); + } + .wplace-btn:active:not(:disabled) { + transform: translateY(0); } .wplace-btn-primary { - background: ${CONFIG.THEME.accent}; + background: linear-gradient(135deg, ${CONFIG.THEME.accent} 0%, #6a5acd 100%); color: white; } .wplace-btn-upload { - background: ${CONFIG.THEME.secondary}; + background: linear-gradient(135deg, ${CONFIG.THEME.secondary} 0%, #4a4a4a 100%); color: white; - border: 1px dashed ${CONFIG.THEME.text}; + border: 1px dashed ${CONFIG.THEME.highlight}; } .wplace-btn-start { - background: ${CONFIG.THEME.success}; + background: linear-gradient(135deg, ${CONFIG.THEME.success} 0%, #228b22 100%); color: white; } .wplace-btn-stop { - background: ${CONFIG.THEME.error}; + background: linear-gradient(135deg, ${CONFIG.THEME.error} 0%, #dc143c 100%); color: white; } .wplace-btn-select { - background: ${CONFIG.THEME.highlight}; - color: black; + background: linear-gradient(135deg, ${CONFIG.THEME.highlight} 0%, #9370db 100%); + color: white; + } + .wplace-btn-file { + background: linear-gradient(135deg, #ff8c00 0%, #ff7f50 100%); + color: white; } .wplace-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; } + .wplace-btn:disabled::before { + display: none; + } .wplace-stats { - background: ${CONFIG.THEME.secondary}; - padding: 12px; + background: rgba(255,255,255,0.03); + padding: 8px; border-radius: 6px; - margin-bottom: 15px; + margin-bottom: 8px; + border: 1px solid rgba(255,255,255,0.1); } .wplace-stat-item { display: flex; justify-content: space-between; - padding: 6px 0; - font-size: 14px; + padding: 4px 0; + font-size: 11px; + border-bottom: 1px solid rgba(255,255,255,0.05); + } + .wplace-stat-item:last-child { + border-bottom: none; } .wplace-stat-label { display: flex; align-items: center; gap: 6px; - opacity: 0.8; + opacity: 0.9; + font-size: 10px; + } + .wplace-stat-value { + font-weight: 600; + color: ${CONFIG.THEME.highlight}; } .wplace-progress { width: 100%; - background: ${CONFIG.THEME.secondary}; + background: rgba(0,0,0,0.3); border-radius: 4px; - margin: 10px 0; + margin: 8px 0; overflow: hidden; + border: 1px solid rgba(255,255,255,0.1); } .wplace-progress-bar { - height: 10px; - background: ${CONFIG.THEME.highlight}; - transition: width 0.3s; + height: 6px; + background: linear-gradient(135deg, ${CONFIG.THEME.highlight} 0%, #9370db 100%); + transition: width 0.5s ease; + position: relative; + } + .wplace-progress-bar::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); + animation: shimmer 2s infinite; + } + @keyframes shimmer { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } } .wplace-status { - padding: 8px; + padding: 6px; border-radius: 4px; text-align: center; - font-size: 13px; + font-size: 11px; } .status-default { background: rgba(255,255,255,0.1); @@ -812,6 +1213,12 @@ ${Utils.t("title")}
+ +
-
- - - - - - - - +
+
+ + +
+
🖼️ Image Management
+
+
+ + +
+
+ +
+
+
+ + +
+
🎮 Painting Control
+
+
+ + +
+
+
+ + +
+
💾 Data Management
+
+
+ + +
+
+ + +
+
+
+ + `; + + // Stats Window - Separate UI + const statsContainer = document.createElement("div"); + statsContainer.id = "wplace-stats-container"; + statsContainer.style.display = "none"; + statsContainer.innerHTML = ` +
+
+ + Painting Stats +
+
+
- -
-
-
- +
+
@@ -868,10 +1343,6 @@
- -
- ${Utils.t("waitingInit")} -
`; @@ -914,6 +1385,46 @@ document.body.appendChild(container); document.body.appendChild(resizeOverlay); document.body.appendChild(resizeContainer); + document.body.appendChild(statsContainer); + + // Query all UI elements after appending to DOM + const initBotBtn = container.querySelector("#initBotBtn"); + const uploadBtn = container.querySelector("#uploadBtn"); + const resizeBtn = container.querySelector("#resizeBtn"); + const selectPosBtn = container.querySelector("#selectPosBtn"); + const startBtn = container.querySelector("#startBtn"); + const stopBtn = container.querySelector("#stopBtn"); + const saveBtn = container.querySelector("#saveBtn"); + const loadBtn = container.querySelector("#loadBtn"); + const saveToFileBtn = container.querySelector("#saveToFileBtn"); + const loadFromFileBtn = container.querySelector("#loadFromFileBtn"); + const minimizeBtn = container.querySelector("#minimizeBtn"); + const compactBtn = container.querySelector("#compactBtn"); + const statsBtn = container.querySelector("#statsBtn"); + const statusText = container.querySelector("#statusText"); + const progressBar = container.querySelector("#progressBar"); + const statsArea = statsContainer.querySelector("#statsArea"); + const content = container.querySelector(".wplace-content"); + const closeStatsBtn = statsContainer.querySelector("#closeStatsBtn"); + + // Check if all elements are found + if (!initBotBtn || !uploadBtn || !selectPosBtn || !startBtn || !stopBtn) { + console.error("Some UI elements not found:", { + initBotBtn: !!initBotBtn, + uploadBtn: !!uploadBtn, + selectPosBtn: !!selectPosBtn, + startBtn: !!startBtn, + stopBtn: !!stopBtn + }); + } + + if (!statsContainer || !statsArea || !closeStatsBtn) { + console.error("Stats UI elements not found:", { + statsContainer: !!statsContainer, + statsArea: !!statsArea, + closeStatsBtn: !!closeStatsBtn + }); + } const header = container.querySelector(".wplace-header"); let pos1 = 0, @@ -929,8 +1440,12 @@ e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; + container.classList.add("wplace-dragging"); document.onmouseup = closeDragElement; document.onmousemove = elementDrag; + + // Prevent text selection during drag + document.body.style.userSelect = "none"; } function elementDrag(e) { @@ -939,28 +1454,105 @@ pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; - container.style.top = container.offsetTop - pos2 + "px"; - container.style.left = container.offsetLeft - pos1 + "px"; + + let newTop = container.offsetTop - pos2; + let newLeft = container.offsetLeft - pos1; + + // Boundary checking to keep UI within viewport + const rect = container.getBoundingClientRect(); + const maxTop = window.innerHeight - rect.height; + const maxLeft = window.innerWidth - rect.width; + + newTop = Math.max(0, Math.min(newTop, maxTop)); + newLeft = Math.max(0, Math.min(newLeft, maxLeft)); + + container.style.top = newTop + "px"; + container.style.left = newLeft + "px"; } function closeDragElement() { + container.classList.remove("wplace-dragging"); document.onmouseup = null; document.onmousemove = null; + document.body.style.userSelect = ""; } - const initBotBtn = container.querySelector("#initBotBtn"); - const uploadBtn = container.querySelector("#uploadBtn"); - const resizeBtn = container.querySelector("#resizeBtn"); - const selectPosBtn = container.querySelector("#selectPosBtn"); - const startBtn = container.querySelector("#startBtn"); - const stopBtn = container.querySelector("#stopBtn"); - const saveBtn = container.querySelector("#saveBtn"); - const loadBtn = container.querySelector("#loadBtn"); - const minimizeBtn = container.querySelector("#minimizeBtn"); - const statusText = container.querySelector("#statusText"); - const progressBar = container.querySelector("#progressBar"); - const statsArea = container.querySelector("#statsArea"); - const content = container.querySelector(".wplace-content"); + function makeDraggable(element) { + let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; + const header = element.querySelector(".wplace-header"); + header.onmousedown = dragMouseDown; + + function dragMouseDown(e) { + if (e.target.closest(".wplace-header-btn")) return; + + e.preventDefault(); + pos3 = e.clientX; + pos4 = e.clientY; + element.classList.add("wplace-dragging"); + document.onmouseup = closeDragElement; + document.onmousemove = elementDrag; + + // Prevent text selection during drag + document.body.style.userSelect = "none"; + } + + function elementDrag(e) { + e.preventDefault(); + pos1 = pos3 - e.clientX; + pos2 = pos4 - e.clientY; + pos3 = e.clientX; + pos4 = e.clientY; + + let newTop = element.offsetTop - pos2; + let newLeft = element.offsetLeft - pos1; + + // Boundary checking to keep UI within viewport + const rect = element.getBoundingClientRect(); + const maxTop = window.innerHeight - rect.height; + const maxLeft = window.innerWidth - rect.width; + + newTop = Math.max(0, Math.min(newTop, maxTop)); + newLeft = Math.max(0, Math.min(newLeft, maxLeft)); + + element.style.top = newTop + "px"; + element.style.left = newLeft + "px"; + } + + function closeDragElement() { + element.classList.remove("wplace-dragging"); + document.onmouseup = null; + document.onmousemove = null; + document.body.style.userSelect = ""; + } + } + + // Make stats container draggable + makeDraggable(statsContainer); + + // Make main container draggable + makeDraggable(container); + + // Stats window functionality + if (statsBtn && closeStatsBtn) { + statsBtn.addEventListener("click", () => { + const isVisible = statsContainer.style.display !== "none"; + if (isVisible) { + statsContainer.style.display = "none"; + statsBtn.innerHTML = ''; + statsBtn.title = "Show Stats"; + } else { + statsContainer.style.display = "block"; + statsBtn.innerHTML = ''; + statsBtn.title = "Hide Stats"; + } + }); + + closeStatsBtn.addEventListener("click", () => { + statsContainer.style.display = "none"; + statsBtn.innerHTML = ''; + statsBtn.title = "Show Stats"; + }); + } const widthSlider = resizeContainer.querySelector("#widthSlider"); const heightSlider = resizeContainer.querySelector("#heightSlider"); @@ -971,16 +1563,142 @@ const confirmResize = resizeContainer.querySelector("#confirmResize"); const cancelResize = resizeContainer.querySelector("#cancelResize"); - minimizeBtn.addEventListener("click", () => { - state.minimized = !state.minimized; - if (state.minimized) { - container.classList.add("wplace-minimized"); - minimizeBtn.innerHTML = ''; - } else { - container.classList.remove("wplace-minimized"); - minimizeBtn.innerHTML = ''; - } - }); + // Compact mode functionality + if (compactBtn) { + compactBtn.addEventListener("click", () => { + container.classList.toggle("wplace-compact"); + const isCompact = container.classList.contains("wplace-compact"); + + if (isCompact) { + compactBtn.innerHTML = ''; + compactBtn.title = "Expand Mode"; + } else { + compactBtn.innerHTML = ''; + compactBtn.title = "Compact Mode"; + } + }); + } + + // Minimize functionality + if (minimizeBtn) { + minimizeBtn.addEventListener("click", () => { + state.minimized = !state.minimized; + if (state.minimized) { + container.classList.add("wplace-minimized"); + content.classList.add("wplace-hidden"); + minimizeBtn.innerHTML = ''; + minimizeBtn.title = "Restore"; + } else { + container.classList.remove("wplace-minimized"); + content.classList.remove("wplace-hidden"); + minimizeBtn.innerHTML = ''; + minimizeBtn.title = "Minimize"; + } + }); + } + + // Save progress functionality + if (saveBtn) { + saveBtn.addEventListener("click", () => { + if (!state.imageLoaded) { + Utils.showAlert(Utils.t("missingRequirements"), "error"); + return; + } + + const success = Utils.saveProgress(); + if (success) { + updateUI("autoSaved", "success"); + Utils.showAlert(Utils.t("autoSaved"), "success"); + } else { + Utils.showAlert("❌ Erro ao salvar progresso", "error"); + } + }); + } + + // Load progress functionality + if (loadBtn) { + loadBtn.addEventListener("click", () => { + const savedData = Utils.loadProgress(); + if (!savedData) { + updateUI("noSavedData", "warning"); + Utils.showAlert(Utils.t("noSavedData"), "warning"); + return; + } + + // Show confirmation dialog + const confirmLoad = confirm( + `${Utils.t("savedDataFound")}\n\n` + + `Saved: ${new Date(savedData.timestamp).toLocaleString()}\n` + + `Progress: ${savedData.state.paintedPixels}/${savedData.state.totalPixels} pixels` + ); + + if (confirmLoad) { + const success = Utils.restoreProgress(savedData); + if (success) { + updateUI("dataLoaded", "success"); + Utils.showAlert(Utils.t("dataLoaded"), "success"); + updateDataButtons(); + + if (!state.colorsChecked) { + initBotBtn.style.display = "block"; + } + + if (state.imageLoaded && state.startPosition && state.region && state.colorsChecked) { + startBtn.disabled = false; + } + } else { + Utils.showAlert("❌ Erro ao carregar progresso", "error"); + } + } + }); + } + + // Save to file functionality + if (saveToFileBtn) { + saveToFileBtn.addEventListener("click", () => { + const success = Utils.saveProgressToFile(); + if (success) { + updateUI("fileSaved", "success"); + Utils.showAlert(Utils.t("fileSaved"), "success"); + } else { + Utils.showAlert(Utils.t("fileError"), "error"); + } + }); + } + + // Load from file functionality + if (loadFromFileBtn) { + loadFromFileBtn.addEventListener("click", async () => { + try { + const success = await Utils.loadProgressFromFile(); + if (success) { + updateUI("fileLoaded", "success"); + Utils.showAlert(Utils.t("fileLoaded"), "success"); + updateDataButtons(); + + // Auto-enable buttons after loading from file + if (state.colorsChecked) { + uploadBtn.disabled = false; + selectPosBtn.disabled = false; + resizeBtn.disabled = false; + initBotBtn.style.display = "none"; + } else { + initBotBtn.style.display = "block"; + } + + if (state.imageLoaded && state.startPosition && state.region && state.colorsChecked) { + startBtn.disabled = false; + } + } + } catch (error) { + if (error.message === 'Invalid JSON file') { + Utils.showAlert(Utils.t("invalidFileFormat"), "error"); + } else { + Utils.showAlert(Utils.t("fileError"), "error"); + } + } + }); + } window.updateUI = (messageKey, type = "default", params = {}) => { const message = Utils.t(messageKey, params); @@ -1017,19 +1735,19 @@
${Utils.t( "progress" )}
-
${progress}%
+
${progress}%
${Utils.t( "pixels" )}
-
${state.paintedPixels}/${state.totalPixels}
+
${state.paintedPixels}/${state.totalPixels}
${Utils.t( "charges" )}
-
${Math.floor(state.currentCharges)}
+
${Math.floor(state.currentCharges)}
${ state.imageLoaded @@ -1038,7 +1756,7 @@
${Utils.t( "estimatedTime" )}
-
${Utils.formatTime(state.estimatedTime)}
+
${Utils.formatTime(state.estimatedTime)}
` : "" @@ -1046,6 +1764,16 @@ `; }; + // Helper function to update data management buttons + window.updateDataButtons = () => { + const hasImageData = state.imageLoaded && state.imageData; + saveBtn.disabled = !hasImageData; + saveToFileBtn.disabled = !hasImageData; + }; + + // Initialize data buttons state + updateDataButtons(); + function showResizeDialog(processor) { const { width, height } = processor.getDimensions(); const aspectRatio = width / height; @@ -1133,42 +1861,45 @@ resizeContainer.style.display = "none"; } - initBotBtn.addEventListener("click", async () => { - try { - updateUI("checkingColors", "default"); + if (initBotBtn) { + initBotBtn.addEventListener("click", async () => { + try { + updateUI("checkingColors", "default"); - state.availableColors = Utils.extractAvailableColors(); + state.availableColors = Utils.extractAvailableColors(); - if (state.availableColors.length === 0) { - Utils.showAlert(Utils.t("noColorsFound"), "error"); - updateUI("noColorsFound", "error"); - return; + if (state.availableColors.length === 0) { + Utils.showAlert(Utils.t("noColorsFound"), "error"); + updateUI("noColorsFound", "error"); + return; + } + + state.colorsChecked = true; + uploadBtn.disabled = false; + selectPosBtn.disabled = false; + initBotBtn.style.display = "none"; + + updateUI("colorsFound", "success", { + count: state.availableColors.length, + }); + updateStats(); + } catch { + updateUI("imageError", "error"); } + }); + } - state.colorsChecked = true; - uploadBtn.disabled = false; - selectPosBtn.disabled = false; - initBotBtn.style.display = "none"; + if (uploadBtn) { + uploadBtn.addEventListener("click", async () => { + try { + updateUI("loadingImage", "default"); + const imageSrc = await Utils.createImageUploader(); - updateUI("colorsFound", "success", { - count: state.availableColors.length, - }); - updateStats(); - } catch { - updateUI("imageError", "error"); - } - }); + const processor = new ImageProcessor(imageSrc); + await processor.load(); - uploadBtn.addEventListener("click", async () => { - try { - updateUI("loadingImage", "default"); - const imageSrc = await Utils.createImageUploader(); - - const processor = new ImageProcessor(imageSrc); - await processor.load(); - - const { width, height } = processor.getDimensions(); - const pixels = processor.getPixelData(); + const { width, height } = processor.getDimensions(); + const pixels = processor.getPixelData(); let totalValidPixels = 0; for (let y = 0; y < height; y++) { @@ -1206,44 +1937,49 @@ startBtn.disabled = false; } - updateStats(); - updateUI("imageLoaded", "success", { count: totalValidPixels }); - } catch { - updateUI("imageError", "error"); - } - }); + updateStats(); + updateDataButtons(); + updateUI("imageLoaded", "success", { count: totalValidPixels }); + } catch { + updateUI("imageError", "error"); + } + }); + } - resizeBtn.addEventListener("click", () => { - if (state.imageLoaded && state.imageData.processor) { - showResizeDialog(state.imageData.processor); - } - }); + if (resizeBtn) { + resizeBtn.addEventListener("click", () => { + if (state.imageLoaded && state.imageData.processor) { + showResizeDialog(state.imageData.processor); + } + }); + } - selectPosBtn.addEventListener("click", async () => { - if (state.selectingPosition) return; + if (selectPosBtn) { + selectPosBtn.addEventListener("click", async () => { + if (state.selectingPosition) return; - state.selectingPosition = true; - state.startPosition = null; - state.region = null; - startBtn.disabled = true; + state.selectingPosition = true; + state.startPosition = null; + state.region = null; + startBtn.disabled = true; - Utils.showAlert(Utils.t("selectPositionAlert"), "info"); - updateUI("waitingPosition", "default"); + Utils.showAlert(Utils.t("selectPositionAlert"), "info"); + updateUI("waitingPosition", "default"); - const originalFetch = window.fetch; + const originalFetch = window.fetch; - window.fetch = async (url, options) => { - if ( - typeof url === "string" && - url.includes("https://backend.wplace.live/s0/pixel/") && - options?.method?.toUpperCase() === "POST" - ) { - try { - const response = await originalFetch(url, options); - const clonedResponse = response.clone(); - const data = await clonedResponse.json(); + window.fetch = async (url, options) => { + if ( + typeof url === "string" && + url.includes("https://backend.wplace.live/s0/pixel/") && + options?.method?.toUpperCase() === "POST" + ) { + try { + const response = await originalFetch(url, options); + const clonedResponse = response.clone(); + const data = await clonedResponse.json(); - if (data?.painted === 1) { + if (data?.painted === 1) { const regionMatch = url.match(/\/pixel\/(\d+)\/(\d+)/); if (regionMatch && regionMatch.length >= 3) { state.region = { @@ -1278,25 +2014,27 @@ return originalFetch(url, options); }; - setTimeout(() => { - if (state.selectingPosition) { - window.fetch = originalFetch; - state.selectingPosition = false; - updateUI("positionTimeout", "error"); - Utils.showAlert(Utils.t("positionTimeout"), "error"); - } - }, 120000); - }); + setTimeout(() => { + if (state.selectingPosition) { + window.fetch = originalFetch; + state.selectingPosition = false; + updateUI("positionTimeout", "error"); + Utils.showAlert(Utils.t("positionTimeout"), "error"); + } + }, 120000); + }); + } - startBtn.addEventListener("click", async () => { + // Function to start painting (can be called programmatically) + async function startPainting() { if (!state.imageLoaded || !state.startPosition || !state.region) { updateUI("missingRequirements", "error"); - return; + return false; } if (!capturedCaptchaToken) { updateUI("captchaNeeded", "error"); Utils.showAlert(Utils.t("captchaNeeded"), "error"); - return; + return false; } state.running = true; @@ -1312,8 +2050,10 @@ try { await processImage(); + return true; } catch { updateUI("paintingError", "error"); + return false; } finally { state.running = false; stopBtn.disabled = true; @@ -1328,74 +2068,27 @@ startBtn.disabled = false; } } - }); + } - stopBtn.addEventListener("click", () => { - state.stopFlag = true; - state.running = false; - stopBtn.disabled = true; - updateUI("paintingStopped", "warning"); - - // Auto save when stopping - if (state.imageLoaded && state.paintedPixels > 0) { - Utils.saveProgress(); - Utils.showAlert(Utils.t("autoSaved"), "success"); - } - }); + if (startBtn) { + startBtn.addEventListener("click", startPainting); + } - saveBtn.addEventListener("click", () => { - if (!state.imageLoaded) { - Utils.showAlert(Utils.t("missingRequirements"), "error"); - return; - } - - const success = Utils.saveProgress(); - if (success) { - updateUI("autoSaved", "success"); - Utils.showAlert(Utils.t("autoSaved"), "success"); - } else { - Utils.showAlert("❌ Erro ao salvar progresso", "error"); - } - }); - - loadBtn.addEventListener("click", () => { - const savedData = Utils.loadProgress(); - if (!savedData) { - updateUI("noSavedData", "warning"); - Utils.showAlert(Utils.t("noSavedData"), "warning"); - return; - } - - // Show confirmation dialog - const confirmLoad = confirm( - `${Utils.t("savedDataFound")}\n\n` + - `Saved: ${new Date(savedData.timestamp).toLocaleString()}\n` + - `Progress: ${savedData.state.paintedPixels}/${savedData.state.totalPixels} pixels` - ); - - if (confirmLoad) { - const success = Utils.restoreProgress(savedData); - if (success) { - updateUI("dataLoaded", "success"); - Utils.showAlert(Utils.t("dataLoaded"), "success"); - - // Enable appropriate buttons - saveBtn.disabled = false; - - // Show init button if colors not checked yet - if (!state.colorsChecked) { - initBotBtn.style.display = "block"; - } - - if (state.imageLoaded && state.startPosition && state.region && state.colorsChecked) { - startBtn.disabled = false; - } - } else { - Utils.showAlert("❌ Erro ao carregar progresso", "error"); + if (stopBtn) { + stopBtn.addEventListener("click", () => { + state.stopFlag = true; + state.running = false; + stopBtn.disabled = true; + updateUI("paintingStopped", "warning"); + + // Auto save when stopping + if (state.imageLoaded && state.paintedPixels > 0) { + Utils.saveProgress(); + Utils.showAlert(Utils.t("autoSaved"), "success"); } - } - }); - + }); + } + // Check for saved progress on startup const checkSavedProgress = () => { const savedData = Utils.loadProgress(); @@ -1591,4 +2284,4 @@ } createUI(); -})(); +})(); \ No newline at end of file diff --git a/FR.md b/FR.md index 5cae58f..bd48b47 100644 --- a/FR.md +++ b/FR.md @@ -2,6 +2,7 @@

WPlace AutoBOT

Ce projet est un fork de https://github.com/DarkModde/WPlace-AutoBOT +Join our discord: https://discord.gg/CBB4abRmGM

Des scripts pratiques avec des menus intuitifs pour vous faciliter la vie sur WPlace !
diff --git a/PT.md b/PT.md index be5d0b3..99e0bab 100644 --- a/PT.md +++ b/PT.md @@ -2,6 +2,7 @@

WPlace AutoBOT

Este projeto foi bifurcado de https://github.com/DarkModde/WPlace-AutoBOT +Junte-se ao nosso discord: https://discord.gg/CBB4abRmGM

Scripts práticos com menus intuitivos para facilitar sua vida no WPlace!
diff --git a/test-save-load.md b/test-save-load.md deleted file mode 100644 index a953055..0000000 --- a/test-save-load.md +++ /dev/null @@ -1,38 +0,0 @@ -# Test Save/Load Progress Feature - -## Các tính năng đã thêm: - -### Auto-Image.js: -1. **Save Progress** - Nút lưu tiến độ thủ công -2. **Load Progress** - Nút khôi phục tiến độ -3. **Auto Save** - Tự động lưu mỗi 50 pixels và khi dừng -4. **Smart UI Restore** - Khôi phục UI đúng trạng thái trước khi dừng -5. **Progress Notification** - Thông báo khi khởi động nếu có progress đã lưu - -### Cách sử dụng: - -1. **Upload ảnh và chọn vị trí** như bình thường -2. **Bắt đầu painting** - script sẽ tự động lưu tiến độ -3. **Dừng script** khi cần - tiến độ sẽ được lưu tự động -4. **Khởi động lại script** - sẽ có thông báo nếu có tiến độ đã lưu -5. **Click "Load Progress"** để tiếp tục từ vị trí đã dừng -6. **UI sẽ hiển thị** đúng trạng thái như trước khi dừng (progress bar, pixels painted, vị trí, etc.) - -### Dữ liệu được lưu: -- Thông tin ảnh và pixels -- Vị trí hiện tại trong painting -- Số pixels đã paint -- Map các pixels đã paint (để tránh paint lại) -- Vị trí bắt đầu và region -- Màu sắc available -- Ngôn ngữ đã chọn - -### Auto Save: -- Mỗi 50 pixels được paint -- Khi user dừng script (Stop button) -- Clear progress khi hoàn thành painting - -### Thông báo: -- Tiếng Việt và English support -- Hiển thị thời gian lưu và tiến độ -- Xác nhận trước khi load