diff --git a/index.html b/index.html index 8960d23..5959289 100644 --- a/index.html +++ b/index.html @@ -43,10 +43,7 @@
- - - - + \ No newline at end of file diff --git a/js/Map.js b/js/Map.js new file mode 100644 index 0000000..ee802c3 --- /dev/null +++ b/js/Map.js @@ -0,0 +1,167 @@ +/** + * Map class for creating interactive maps with SVG + */ +export default class Map { + /** + * @param {string} svgId - The ID of the SVG element + * @param {string} mapImage - The URL of the map image + */ + constructor(svgId, mapImage) { + this.svg = document.getElementById(svgId); + this.svg.innerHTML = ""; + + this.g = document.createElementNS("http://www.w3.org/2000/svg", "g"); + this.svg.appendChild(this.g); + + this.g.innerHTML += ``; + + this.isDragging = false; + this.startX = 0; + this.startY = 0; + this.initialTranslateX = 0; + this.initialTranslateY = 0; + this.initialScale = 1; + + this.dragSensitivity = 2; + this.maxScale = 6; + this.minScale = 1; + + this.initializeEvents(); + } + + initializeEvents() { + this.svg.addEventListener("mousedown", (e) => this.onMouseDown(e)); + this.svg.addEventListener("mousemove", (e) => this.onMouseMove(e)); + this.svg.addEventListener("mouseup", () => this.onMouseUp()); + this.svg.addEventListener("mouseleave", () => this.onMouseUp()); + this.svg.addEventListener("wheel", (e) => this.onWheel(e)); + } + + /** + * Set the zoom level + * @param {number} zoomLevel - The zoom level + */ + setZoomLevel(zoomLevel) { + const svgRect = this.svg.getBoundingClientRect(); + const centerX = svgRect.width / 2; + const centerY = svgRect.height / 2; + + const [translate, scale] = this.parseTransform(this.g.getAttribute("transform") || "translate(0,0) scale(1)"); + const newScale = Math.min(Math.max(zoomLevel, this.minScale), this.maxScale); + + const svgPoint = this.svg.createSVGPoint(); + svgPoint.x = centerX; + svgPoint.y = centerY; + const centerPoint = svgPoint.matrixTransform(this.svg.getScreenCTM().inverse()); + + const newTranslateX = translate[0] - (centerPoint.x - translate[0]) * (newScale / scale - 1); + const newTranslateY = translate[1] - (centerPoint.y - translate[1]) * (newScale / scale - 1); + + this.g.setAttribute("transform", `translate(${newTranslateX}, ${newTranslateY}) scale(${newScale})`); + } + + /** + * Zoom in + * @param factor - The zoom factor + */ + zoomIn(factor = 1.1) { + const [translate, scale] = this.parseTransform(this.g.getAttribute("transform") || "translate(0,0) scale(1)"); + const newScale = Math.min(scale * factor, this.maxScale); + this.setZoomLevel(newScale); + } + + /** + * Zoom out + * @param {number} factor - The zoom factor + */ + zoomOut(factor = 0.9) { + const [translate, scale] = this.parseTransform(this.g.getAttribute("transform") || "translate(0,0) scale(1)"); + const newScale = Math.max(scale * factor, this.minScale); + this.setZoomLevel(newScale); + } + + onMouseDown(e) { + if (!this.g.getAttribute("transform") || this.g.getAttribute("transform").includes("scale(1)")) return; + this.isDragging = true; + this.startX = e.clientX; + this.startY = e.clientY; + + const [translate, scale] = this.parseTransform(this.g.getAttribute("transform") || "translate(0,0) scale(1)"); + this.initialTranslateX = translate[0]; + this.initialTranslateY = translate[1]; + this.initialScale = scale; + } + + onMouseMove(e) { + if (!this.isDragging) return; + + const svgRect = this.svg.getBoundingClientRect(); + const mapRect = this.g.getBBox(); + + const deltaX = (e.clientX - this.startX) * this.dragSensitivity; + const deltaY = (e.clientY - this.startY) * this.dragSensitivity; + + let newTranslateX = this.initialTranslateX + deltaX; + let newTranslateY = this.initialTranslateY + deltaY; + + const scale = this.initialScale; + const mapWidth = mapRect.width * scale; + const mapHeight = mapRect.height * scale; + const svgWidth = svgRect.width; + const svgHeight = svgRect.height; + + const minTranslateX = Math.min(0, svgWidth - mapWidth); + const minTranslateY = Math.min(0, svgHeight - mapHeight); + + const maxTranslateX = 0; + const maxTranslateY = 0; + + newTranslateX = Math.max(minTranslateX, Math.min(maxTranslateX, newTranslateX)); + newTranslateY = Math.max(minTranslateY, Math.min(maxTranslateY, newTranslateY)); + + this.g.setAttribute("transform", `translate(${newTranslateX}, ${newTranslateY}) scale(${scale})`); + } + + onMouseUp() { + this.isDragging = false; + } + + onWheel(e) { + e.preventDefault(); + + const [translate, scale] = this.parseTransform(this.g.getAttribute("transform") || "translate(0,0) scale(1)"); + const newScale = Math.min(Math.max(scale * (e.deltaY > 0 ? 0.9 : 1.1), 1), this.maxScale); + + if (newScale === 1) { + this.g.setAttribute("transform", `translate(0,0) scale(1)`); + } else { + const svgPoint = this.svg.createSVGPoint(); + svgPoint.x = e.clientX; + svgPoint.y = e.clientY; + const mousePoint = svgPoint.matrixTransform(this.svg.getScreenCTM().inverse()); + + const newTranslateX = translate[0] - (mousePoint.x - translate[0]) * (newScale / scale - 1); + const newTranslateY = translate[1] - (mousePoint.y - translate[1]) * (newScale / scale - 1); + this.g.setAttribute("transform", `translate(${newTranslateX}, ${newTranslateY}) scale(${newScale})`); + } + } + + parseTransform(transform) { + const translateMatch = transform.match(/translate\(([^)]+)\)/); + const scaleMatch = transform.match(/scale\(([^)]+)\)/); + const translate = translateMatch ? translateMatch[1].split(",").map(Number) : [0, 0]; + const scale = scaleMatch ? parseFloat(scaleMatch[1]) : 1; + return [translate, scale]; + } + + addPolygon(points, attributes = {}) { + const polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon"); + polygon.setAttribute("points", points); + Object.entries(attributes).forEach(([key, value]) => polygon.setAttribute(key, value)); + this.g.appendChild(polygon); + } + + addSVGElement(svgString) { + this.g.innerHTML += svgString; + } +} \ No newline at end of file diff --git a/js/SlimeMap.js b/js/SlimeMap.js new file mode 100644 index 0000000..bbe5c35 --- /dev/null +++ b/js/SlimeMap.js @@ -0,0 +1,69 @@ +import Map from "./Map.js"; + +export default class SlimeMap extends Map { + /** + * @param {string} svgId - The ID of the SVG element + * @param {string} mapImage - The URL of the map image + * @param nations + * @param capitals + * @param pois + */ + constructor(svgId, mapImage, nations, capitals, pois) { + super(svgId, mapImage); + this.nations = nations; + this.capitals = capitals; + this.pois = pois; + this.addNations(); + this.addCapitals(); + this.addPOIs(); + } + + addNations() { + for (let nation of this.nations) { + this.addPolygon(nation.points, { + class: "region", + fill: "transparent", + stroke: "transparent", + "stroke-width": "4", + "data-name": nation.name, + "data-ruler": nation.ruler, + "data-capital": nation.capital, + "data-population": nation.population, + "data-description": nation.description, + "data-url": nation.url ?? "" + }); + } + } + + addCapitals() { + for (let capital of this.capitals) { + const svgString = ` + + + + `; + this.addSVGElement(svgString); + } + } + + addPOIs() { + for (let poi of this.pois) { + const svgString = ` + + + + `; + this.addSVGElement(svgString); + } + } +} \ No newline at end of file diff --git a/js/locations/capitals.js b/js/locations/capitals.js index 504fa8d..18f5aa3 100644 --- a/js/locations/capitals.js +++ b/js/locations/capitals.js @@ -9,7 +9,7 @@ /** * @type {[CapitalData]} */ -const capitals = [ +export const capitals = [ { name: "Lune", description: "The capital of the Holy Empire Lubelius, home of God Luminous.", diff --git a/js/locations/index.js b/js/locations/index.js new file mode 100644 index 0000000..8943a87 --- /dev/null +++ b/js/locations/index.js @@ -0,0 +1,3 @@ +export * from "./pois.js"; +export * from "./nations.js"; +export * from "./capitals.js"; \ No newline at end of file diff --git a/js/locations/nations.js b/js/locations/nations.js index 75ae2e0..8f6f69a 100644 --- a/js/locations/nations.js +++ b/js/locations/nations.js @@ -12,7 +12,7 @@ /** * @type {[NationData]} */ -const nations = [ +export const nations = [ { name: "Kingdom of Englassia", url: "https://tensura.fandom.com/wiki/Kingdom_of_Englassia", diff --git a/js/locations/pois.js b/js/locations/pois.js index 4a915f4..1a5329d 100644 --- a/js/locations/pois.js +++ b/js/locations/pois.js @@ -9,7 +9,7 @@ /** * @type {[POIData]} */ -const pois = [ +export const pois = [ { name: "Sealed Cave", description: "The cave where Veldora was sealed and where Rimuru was born.", diff --git a/js/mapBuilder.js b/js/mapBuilder.js index 4e5e6b0..f16923a 100644 --- a/js/mapBuilder.js +++ b/js/mapBuilder.js @@ -1,184 +1,4 @@ -function drawMap() { - const svg = document.getElementById("map"); - svg.innerHTML = ""; +import {pois, capitals, nations} from "./locations"; +import SlimeMap from "./SlimeMap.js"; - const g = document.createElementNS("http://www.w3.org/2000/svg", "g"); - svg.appendChild(g); - - g.innerHTML += ``; - - for (let nation of nations) { - addNation(g, nation); - } - - for (let capital of capitals) { - addCapital(g, capital); - } - - for (let poi of pois) { - addPOI(g, poi); - } - - let isDragging = false; - let startX, startY, initialTranslateX, initialTranslateY, initialScale; - - const dragSensitivity = 2; - const maxScale = 6; - - svg.addEventListener("mousedown", (e) => { - if (!g.getAttribute("transform") || g.getAttribute("transform").includes("scale(1)")) return; - isDragging = true; - startX = e.clientX; - startY = e.clientY; - - // Get the current transform values, including scale - const [translate, scale] = parseTransform(g.getAttribute("transform") || "translate(0,0) scale(1)"); - initialTranslateX = translate[0]; - initialTranslateY = translate[1]; - initialScale = scale; - }); - - svg.addEventListener("mousemove", (e) => { - if (!isDragging) return; - - const svgRect = svg.getBoundingClientRect(); - const mapRect = g.getBBox(); // Get the bounding box of the map content - - // Calculate how far the mouse has moved - const deltaX = (e.clientX - startX) * dragSensitivity; - const deltaY = (e.clientY - startY) * dragSensitivity; - - // New translation values - let newTranslateX = initialTranslateX + deltaX; - let newTranslateY = initialTranslateY + deltaY; - - // Calculate boundaries - const scale = initialScale; - const mapWidth = mapRect.width * scale; - const mapHeight = mapRect.height * scale; - const svgWidth = svgRect.width; - const svgHeight = svgRect.height; - - // Constrain the translation to prevent dragging out of bounds - const minTranslateX = Math.min(0, svgWidth - mapWidth); // Minimum X translation - const minTranslateY = Math.min(0, svgHeight - mapHeight); // Minimum Y translation - - const maxTranslateX = 0; // Maximum X translation - const maxTranslateY = 0; // Maximum Y translation - - // Apply constraints - newTranslateX = Math.max(minTranslateX, Math.min(maxTranslateX, newTranslateX)); - newTranslateY = Math.max(minTranslateY, Math.min(maxTranslateY, newTranslateY)); - - // Apply the movement to the translation, using the stored initialScale - g.setAttribute("transform", `translate(${newTranslateX}, ${newTranslateY}) scale(${scale})`); - }); - - svg.addEventListener("mouseup", () => { - isDragging = false; - }); - - svg.addEventListener("mouseleave", () => { - isDragging = false; - }); - - svg.addEventListener("wheel", (e) => { - e.preventDefault(); - - const [translate, scale] = parseTransform(g.getAttribute("transform") || "translate(0,0) scale(1)"); - const newScale = Math.min(Math.max(scale * (e.deltaY > 0 ? 0.9 : 1.1), 1), maxScale); - - if (newScale === 1) { - // Center the map when zoomed out - g.setAttribute("transform", `translate(0,0) scale(1)`); - } else { - // Convert mouse position to SVG coordinates - const svgPoint = svg.createSVGPoint(); - svgPoint.x = e.clientX; - svgPoint.y = e.clientY; - const mousePoint = svgPoint.matrixTransform(svg.getScreenCTM().inverse()); - - // Adjust translation based on new scale - const newTranslateX = translate[0] - (mousePoint.x - translate[0]) * (newScale / scale - 1); - const newTranslateY = translate[1] - (mousePoint.y - translate[1]) * (newScale / scale - 1); - g.setAttribute("transform", `translate(${newTranslateX}, ${newTranslateY}) scale(${newScale})`); - } - }); - - function parseTransform(transform) { - const translateMatch = transform.match(/translate\(([^)]+)\)/); - const scaleMatch = transform.match(/scale\(([^)]+)\)/); - const translate = translateMatch ? translateMatch[1].split(",").map(Number) : [0, 0]; - const scale = scaleMatch ? parseFloat(scaleMatch[1]) : 1; - return [translate, scale]; - } -} - -/** - * @param {Element} svg - * @param {NationData} nation - */ -function addNation(svg, nation) { - const polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon"); - polygon.setAttribute("class", "region"); - polygon.setAttribute("fill", "transparent"); - polygon.setAttribute("stroke", "transparent"); - polygon.setAttribute("stroke-width", "4"); - polygon.setAttribute("points", nation.points); - polygon.setAttribute("data-name", nation.name); - polygon.setAttribute("data-ruler", nation.ruler); - polygon.setAttribute("data-capital", nation.capital); - polygon.setAttribute("data-population", nation.population); - polygon.setAttribute("data-description", nation.description); - polygon.setAttribute("data-url", nation.url ?? ""); - svg.appendChild(polygon); -} - -/** - * @param {Element} svg - * @param {CapitalData} capital - */ -function addCapital(svg, capital) { - const svgString = ` - - - - - `; - - svg.innerHTML += svgString; -} - -/** - * @param {Element} svg - * @param {POIData} poi - */ -function addPOI(svg, poi) { - const svgString = ` - - - - - `; - - svg.innerHTML += svgString; -} - -drawMap(); \ No newline at end of file +const slimeMap = new SlimeMap("map", "img/map.webp", nations, capitals, pois); \ No newline at end of file diff --git a/js/tooltip.js b/js/tooltip.js index cdf0683..3dbe6b0 100644 --- a/js/tooltip.js +++ b/js/tooltip.js @@ -4,6 +4,10 @@ document.addEventListener("DOMContentLoaded", () => { let mouseOnTooltipableArea = false; let currentTooltipContent = ''; + setInterval(() => { + console.log("Control is held:", isControlHeld); + }, 500) + const showTooltip = (content, e) => { mouseOnTooltipableArea = true; currentTooltipContent = content;