288 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			288 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | const canvas = document.getElementById("mapCanvas"); | ||
|  | const fogCanvas = document.createElement("canvas"); | ||
|  | const ctx = canvas.getContext("2d"); | ||
|  | const fogCtx = fogCanvas.getContext("2d"); | ||
|  | const iframe = document.getElementById("worldmap-iframe"); | ||
|  | 
 | ||
|  | let image = null; | ||
|  | let offsetX = 0, offsetY = 0; | ||
|  | let scale = 1; | ||
|  | let isDragging = false; | ||
|  | let startX, startY; | ||
|  | let fogEnabled = false; | ||
|  | let fogMode = "erase"; | ||
|  | let brushSize = 50; | ||
|  | let mouseX = 0, mouseY = 0; | ||
|  | 
 | ||
|  | const channel = new BroadcastChannel("mapsync"); | ||
|  | let MapX = 0; | ||
|  | let MapY = 0; | ||
|  | let MapZoom = 2; | ||
|  | 
 | ||
|  | function resizeCanvas() { | ||
|  |   canvas.width = window.innerWidth; | ||
|  |   canvas.height = window.innerHeight; | ||
|  |   draw(); | ||
|  |   if (fogEnabled) drawBrushPreview(mouseX, mouseY); | ||
|  | } | ||
|  | window.addEventListener("resize", resizeCanvas); | ||
|  | resizeCanvas(); | ||
|  | 
 | ||
|  | // === Fog Controls ===
 | ||
|  | document.getElementById("toggleFog").addEventListener("change", (e) => { | ||
|  |   fogEnabled = e.target.checked; | ||
|  |   channel.postMessage({ type: "fogEnabled", enabled: fogEnabled }); | ||
|  |   draw(); | ||
|  |   if (fogEnabled) drawBrushPreview(mouseX, mouseY); | ||
|  | }); | ||
|  | 
 | ||
|  | document.getElementById("fogMode").addEventListener("click", () => { | ||
|  |   fogMode = fogMode === "erase" ? "paint" : "erase"; | ||
|  |   document.getElementById("fogMode").innerText = fogMode === "erase" ? "Modus: Radierer" : "Modus: Nebel"; | ||
|  | }); | ||
|  | 
 | ||
|  | document.getElementById("fogClear").addEventListener("click", () => { | ||
|  |   const btn = document.getElementById("fogClear"); | ||
|  |   btn.disabled = true; | ||
|  |   setTimeout(() => btn.disabled = false, 1000); | ||
|  |   fogCtx.clearRect(0, 0, fogCanvas.width, fogCanvas.height); | ||
|  |   syncFog(); draw(); | ||
|  |   if (fogEnabled) drawBrushPreview(mouseX, mouseY); | ||
|  | }); | ||
|  | 
 | ||
|  | document.getElementById("fogFull").addEventListener("click", () => { | ||
|  |   const btn = document.getElementById("fogFull"); | ||
|  |   btn.disabled = true; | ||
|  |   setTimeout(() => btn.disabled = false, 1000); | ||
|  |   fogCtx.fillStyle = "rgba(50, 50, 50, 1)"; | ||
|  |   fogCtx.fillRect(0, 0, fogCanvas.width, fogCanvas.height); | ||
|  |   syncFog(); draw(); | ||
|  |   if (fogEnabled) drawBrushPreview(mouseX, mouseY); | ||
|  | }); | ||
|  | 
 | ||
|  | document.getElementById("brushSize").addEventListener("input", (e) => { | ||
|  |   brushSize = 50 * parseInt(e.target.value); | ||
|  | }); | ||
|  | 
 | ||
|  | // === Mouse Interaktion ===
 | ||
|  | canvas.addEventListener("dragover", e => e.preventDefault()); | ||
|  | canvas.addEventListener("drop", e => { | ||
|  |   e.preventDefault(); | ||
|  |   const file = e.dataTransfer.files[0]; | ||
|  |   const reader = new FileReader(); | ||
|  |   reader.onload = evt => { | ||
|  |     const img = new Image(); | ||
|  |     img.onload = () => { | ||
|  |       image = img; | ||
|  |       //Fog auf Größe des Geladenen Bildes anpassen.
 | ||
|  |       fogCanvas.width = img.width; | ||
|  |       fogCanvas.height = img.height; | ||
|  |       fogCtx.clearRect(0, 0, fogCanvas.width, fogCanvas.height); | ||
|  |       draw(); | ||
|  |       if (fogEnabled) drawBrushPreview(mouseX, mouseY); | ||
|  |       channel.postMessage({ type: "mapData", data: evt.target.result }); | ||
|  |       channel.postMessage({ type: "fogEnabled", enabled: fogEnabled }); | ||
|  |       syncFog(); | ||
|  |     }; | ||
|  |     img.src = evt.target.result; | ||
|  |   }; | ||
|  |   reader.readAsDataURL(file); | ||
|  | }); | ||
|  | 
 | ||
|  | function LoadImageMap(i) { | ||
|  |   const img = new Image(); | ||
|  |   img.onload = () => { | ||
|  |     image = img; | ||
|  |     fogCanvas.width = img.width; | ||
|  |     fogCanvas.height = img.height; | ||
|  |     fogCtx.clearRect(0, 0, fogCanvas.width, fogCanvas.height); | ||
|  |     draw(); | ||
|  |     if (fogEnabled) drawBrushPreview(mouseX, mouseY); | ||
|  |     channel.postMessage({ type: "mapData", data: i }); | ||
|  |     channel.postMessage({ type: "fogEnabled", enabled: fogEnabled }); | ||
|  |     syncFog(); | ||
|  |   }; | ||
|  |   img.src = i; | ||
|  | } | ||
|  | 
 | ||
|  | window.LoadImageMap = LoadImageMap; | ||
|  | 
 | ||
|  | canvas.addEventListener("mousedown", e => { | ||
|  |   if (e.button === 1) { | ||
|  |     isDragging = true; | ||
|  |     startX = e.clientX - offsetX; | ||
|  |     startY = e.clientY - offsetY; | ||
|  |   } | ||
|  | }); | ||
|  | canvas.addEventListener("mouseup", () => { isDragging = false; }); | ||
|  | canvas.addEventListener("mouseout", () => { isDragging = false; }); | ||
|  | canvas.addEventListener("mousemove", e => { | ||
|  |   mouseX = e.clientX; | ||
|  |   mouseY = e.clientY; | ||
|  |   if (fogEnabled) draw(); | ||
|  |   if (isDragging) { | ||
|  |     offsetX = e.clientX - startX; | ||
|  |     offsetY = e.clientY - startY; | ||
|  |     syncView(); | ||
|  |     draw(); | ||
|  |     if (fogEnabled) drawBrushPreview(mouseX, mouseY); | ||
|  |   } else if (fogEnabled && e.buttons === 1) { | ||
|  |     const x = (mouseX - offsetX) / scale; | ||
|  |     const y = (mouseY - offsetY) / scale; | ||
|  |     fogCtx.globalCompositeOperation = fogMode === "erase" ? "destination-out" : "source-over"; | ||
|  |     fogCtx.fillStyle = "rgba(50, 50, 50, 0.9)"; | ||
|  |     fogCtx.beginPath(); | ||
|  |     fogCtx.arc(x, y, brushSize, 0, 2 * Math.PI); | ||
|  |     fogCtx.fill(); | ||
|  |     syncFog(); draw(); | ||
|  |     drawBrushPreview(mouseX, mouseY); | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | canvas.addEventListener("wheel", e => { | ||
|  |   e.preventDefault(); | ||
|  |   const worldX = (e.clientX - offsetX) / scale; | ||
|  |   const worldY = (e.clientY - offsetY) / scale; | ||
|  |   const zoomFactor = e.deltaY < 0 ? 1.1 : 0.9; | ||
|  |   scale *= zoomFactor; | ||
|  |   offsetX = e.clientX - worldX * scale; | ||
|  |   offsetY = e.clientY - worldY * scale; | ||
|  |   syncView(); draw(); | ||
|  |   if (fogEnabled) drawBrushPreview(mouseX, mouseY); | ||
|  | }); | ||
|  | 
 | ||
|  | function syncView() { | ||
|  |   channel.postMessage({ type: "viewData", offsetX, offsetY, scale }); | ||
|  | } | ||
|  | 
 | ||
|  | function syncFog() { | ||
|  |   const fogURL = fogCanvas.toDataURL("image/png"); | ||
|  |   channel.postMessage({ type: "fogData", data: fogURL }); | ||
|  | } | ||
|  | 
 | ||
|  | function draw() { | ||
|  |   ctx.setTransform(scale, 0, 0, scale, offsetX, offsetY); | ||
|  |   ctx.clearRect(-offsetX / scale, -offsetY / scale, canvas.width / scale, canvas.height / scale); | ||
|  |   if (image) ctx.drawImage(image, 0, 0); | ||
|  |   if (fogEnabled && fogCanvas.width && fogCanvas.height) { | ||
|  |     ctx.globalAlpha = 0.4; | ||
|  |     ctx.drawImage(fogCanvas, 0, 0); | ||
|  |     ctx.globalAlpha = 1; | ||
|  |   } | ||
|  |   if (fogEnabled) drawBrushPreview(mouseX, mouseY); | ||
|  | } | ||
|  | 
 | ||
|  | function drawBrushPreview(mouseX, mouseY) { | ||
|  |   const worldX = (mouseX - offsetX) / scale; | ||
|  |   const worldY = (mouseY - offsetY) / scale; | ||
|  |   ctx.save(); | ||
|  |   ctx.setTransform(scale, 0, 0, scale, offsetX, offsetY); | ||
|  |   ctx.strokeStyle = "rgba(255,255,255,0.8)"; | ||
|  |   ctx.lineWidth = 2 / scale; | ||
|  |   ctx.beginPath(); | ||
|  |   ctx.arc(worldX, worldY, brushSize, 0, 2 * Math.PI); | ||
|  |   ctx.stroke(); | ||
|  |   ctx.restore(); | ||
|  | } | ||
|  | 
 | ||
|  | window.addEventListener("keydown", (e) => { | ||
|  |   if (e.key.toLowerCase() === "f") { | ||
|  |     fogEnabled = !fogEnabled; | ||
|  |     document.getElementById("toggleFog").checked = fogEnabled; | ||
|  |     channel.postMessage({ type: "fogEnabled", enabled: fogEnabled }); | ||
|  |     draw(); | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | document.getElementById("toggleHexgrid").addEventListener("change", (e) => { | ||
|  |   channel.postMessage({ type: "hexgrid", enabled: e.target.checked }); | ||
|  | }); | ||
|  | 
 | ||
|  | document.getElementById("gridScaleSlider").addEventListener("input", (e) => { | ||
|  |   const scale = parseFloat(e.target.value); | ||
|  |   channel.postMessage({ type: "hexgridScale", scale }); | ||
|  | }); | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | // === Weltkarte Funktionen ===
 | ||
|  | let worldMapMode = false; | ||
|  | let previousImageSrc = null; | ||
|  | 
 | ||
|  | // === Leaflet View Update Intervall ===
 | ||
|  | setInterval(() => { | ||
|  |   const iframe = document.getElementById("worldmap-iframe"); | ||
|  |   if (iframe?.contentWindow && worldMapMode) { | ||
|  |     iframe.contentWindow.postMessage("getView", "*"); | ||
|  |   } | ||
|  | }, 500); | ||
|  | 
 | ||
|  | window.addEventListener("message", (event) => { | ||
|  |   const data = event.data; | ||
|  |   if (data?.type === "viewData") { | ||
|  |     MapX = data.center.lng; | ||
|  |     MapY = data.center.lat; | ||
|  |     MapZoom = data.zoom; | ||
|  |     channel.postMessage({ type: "MapCoord", MapX, MapY, MapZoom}); //Dem Slave die Karten Werte schicken.
 | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | const mapButton = document.getElementById("mapToggleButton"); | ||
|  | if (mapButton) { | ||
|  |   mapButton.addEventListener("click", () => { | ||
|  |     if (!worldMapMode) { | ||
|  |       previousImageSrc = image?.src || "pause.png"; | ||
|  |       loadMap(); | ||
|  |       channel.postMessage({ type: "mapMode", enabled: true }); | ||
|  |       mapButton.textContent = "↩️"; | ||
|  |       worldMapMode = true; | ||
|  |       syncWorldMapView(); | ||
|  |     } else { | ||
|  |       restoreImage(previousImageSrc); | ||
|  |       channel.postMessage({ type: "mapMode", enabled: false }); | ||
|  |       mapButton.textContent = "🗺"; | ||
|  |       worldMapMode = false; | ||
|  |     } | ||
|  |   }); | ||
|  | } | ||
|  | 
 | ||
|  | //Laden der iframe WorldMap
 | ||
|  | function loadMap() { | ||
|  |   const iframe = document.getElementById("worldmap-iframe"); | ||
|  |   if (!iframe) return; | ||
|  |   iframe.style.display = "block"; | ||
|  |   if (iframe.contentWindow) { | ||
|  |     iframe.contentWindow.postMessage({ action: "syncRequest" }, "*"); | ||
|  |   } else { | ||
|  |     iframe.onload = () => { | ||
|  |       iframe.contentWindow.postMessage({ action: "syncRequest" }, "*"); | ||
|  |     }; | ||
|  |   } | ||
|  | } | ||
|  | //Nach dem schließen der iframe WorldMap, wieder die letzte Map herstellen.
 | ||
|  | function restoreImage(src) { | ||
|  |   const iframe = document.getElementById("worldmap-iframe"); | ||
|  |   const canvas = document.getElementById("mapCanvas"); | ||
|  | 
 | ||
|  |   if (iframe) iframe.style.display = "none"; | ||
|  |   if (canvas) canvas.style.display = "block"; | ||
|  |   if (!src) return; | ||
|  | 
 | ||
|  |   const img = new Image(); | ||
|  |   img.onload = () => { | ||
|  |     image = img; | ||
|  |     fogCanvas.width = img.width; | ||
|  |     fogCanvas.height = img.height; | ||
|  |     fogCtx.clearRect(0, 0, fogCanvas.width, fogCanvas.height); | ||
|  |     draw(); | ||
|  |   }; | ||
|  |   img.src = src; | ||
|  | } | ||
|  | 
 | ||
|  | function syncWorldMapView() { | ||
|  |   const iframe = document.getElementById("worldmap-iframe"); | ||
|  |   if (worldMapMode && iframe?.contentWindow) { | ||
|  |     iframe.contentWindow.postMessage("getView", "*"); | ||
|  |   } | ||
|  | } |