diff --git a/public/main.js b/public/main.js index 6786852a6..8b2b4c6f9 100644 --- a/public/main.js +++ b/public/main.js @@ -223,15 +223,15 @@ function zoomRaf() { } if (didScaleChange) { - postZoom(); + invokeActiveZooming(); + drawScaleBar(scaleBar, scale); + fitScaleBar(scaleBar, svgWidth, svgHeight); } - }) -} -const postZoom = () => { - invokeActiveZooming(); - drawScaleBar(scaleBar, scale); - fitScaleBar(scaleBar, svgWidth, svgHeight); + if (didPositionChange || didScaleChange) { + window.updateMinimap && updateMinimap(); + } + }); } const zoom = d3.zoom().scaleExtent([1, 20]).on("zoom", zoomRaf); diff --git a/public/modules/ui/minimap.js b/public/modules/ui/minimap.js new file mode 100644 index 000000000..bbc8f3613 --- /dev/null +++ b/public/modules/ui/minimap.js @@ -0,0 +1,133 @@ +"use strict"; + +let minimapInitialized = false; + +export function openMinimapDialog() { + closeDialogs("#minimap, .stable"); + ensureMinimapStyles(); + ensureMinimapMarkup(); + + updateMinimap(); + + $("#minimap").dialog({ + title: "Minimap", + resizable: false, + width: "auto", + position: {my: "left bottom", at: "left+10 bottom-25", of: "svg", collision: "fit"}, + open: function () { + $(this).parent().addClass("minimap-dialog"); + }, + close: function () { + $(this).dialog("destroy"); + } + }); +} + +function ensureMinimapStyles() { + if (byId("minimapStyles")) return; + + const style = document.createElement("style"); + style.id = "minimapStyles"; + style.textContent = /* css */ ` + .minimap-dialog .ui-dialog-content { + padding: 0 !important; + overflow: hidden; + } + + #minimap { + padding: 0 !important; + background: transparent; + } + + #minimapViewportWrap { + position: relative; + width: 20em; + border: 0; + } + + #minimapSurface { + display: block; + width: 100%; + height: auto; + cursor: crosshair; + } + + #minimapMapUse { + pointer-events: none; + } + + #minimapViewport { + fill: rgba(190, 255, 137, 0.1); + stroke: #624954; + stroke-width: 1; + stroke-dasharray: 4; + vector-effect: non-scaling-stroke; + pointer-events: none; + } + `; + + document.head.append(style); +} + +function ensureMinimapMarkup() { + if (minimapInitialized) return; + + const container = byId("minimapContent"); + if (!container) return; + + minimapInitialized = true; + container.innerHTML = /* html */ ` +
+ + + + +
+ `; + + byId("minimapSurface")?.addEventListener("click", minimapClickToPan); + window.updateMinimap = updateMinimap; +} + +function minimapClickToPan(event) { + const minimap = byId("minimapSurface"); + if (!minimap) return; + + const point = minimap.createSVGPoint(); + point.x = event.clientX; + point.y = event.clientY; + + const ctm = minimap.getScreenCTM(); + if (!ctm) return; + + const svgPoint = point.matrixTransform(ctm.inverse()); + const x = minmax(svgPoint.x, 0, graphWidth); + const y = minmax(svgPoint.y, 0, graphHeight); + zoomTo(x, y, scale, 450); +} + +function updateMinimap() { + const minimap = byId("minimapSurface"); + const viewport = byId("minimapViewport"); + const mapUse = byId("minimapMapUse"); + if (!minimap || !viewport || !mapUse) return; + + minimap.setAttribute("viewBox", `0 0 ${graphWidth} ${graphHeight}`); + + // #viewbox already has the current transform; invert it in minimap to show the whole world map. + const inverseScale = scale ? 1 / scale : 1; + mapUse.setAttribute( + "transform", + `translate(${rn(-viewX * inverseScale, 3)} ${rn(-viewY * inverseScale, 3)}) scale(${rn(inverseScale, 6)})` + ); + + const left = Math.max(0, -viewX * inverseScale); + const top = Math.max(0, -viewY * inverseScale); + const right = Math.min(graphWidth, left + svgWidth * inverseScale); + const bottom = Math.min(graphHeight, top + svgHeight * inverseScale); + + viewport.setAttribute("x", rn(left, 3)); + viewport.setAttribute("y", rn(top, 3)); + viewport.setAttribute("width", rn(Math.max(0, right - left), 3)); + viewport.setAttribute("height", rn(Math.max(0, bottom - top), 3)); +} diff --git a/public/modules/ui/tools.js b/public/modules/ui/tools.js index 34c1cd120..c98fa28c9 100644 --- a/public/modules/ui/tools.js +++ b/public/modules/ui/tools.js @@ -27,6 +27,7 @@ toolsContent.addEventListener("click", function (event) { else if (button === "overviewMilitaryButton") overviewMilitary(); else if (button === "overviewMarkersButton") overviewMarkers(); else if (button === "overviewCellsButton") viewCellDetails(); + else if (button === "openMinimapButton") openMinimap(); // click on Regenerate buttons if (event.target.parentNode.id === "regenerateFeature") { @@ -327,8 +328,8 @@ function recreateStates() { const type = nomadic ? "Nomadic" : pack.cultures[culture].type === "Nomadic" - ? "Generic" - : pack.cultures[culture].type; + ? "Generic" + : pack.cultures[culture].type; const expansionism = rn(Math.random() * byId("sizeVariety").value + 1, 1); const cultureType = pack.cultures[culture].type; @@ -898,8 +899,8 @@ function configMarkersGeneration() { + isExternal ? "" : "hidden" + } style="width:1.2em; height:1.2em; vertical-align: middle;"> ${isExternal ? "" : icon} @@ -987,3 +988,8 @@ async function overviewCharts() { const Overview = await import("../dynamic/overview/charts-overview.js?v=1.99.00"); Overview.open(); } + +async function openMinimap() { + const Minimap = await import("./minimap.js?v=1.99.00"); + Minimap.openMinimapDialog(); +} diff --git a/src/index.html b/src/index.html index 2a74b3a73..623bafd40 100644 --- a/src/index.html +++ b/src/index.html @@ -2269,6 +2269,9 @@ > Charts +
Create
@@ -6004,6 +6007,10 @@ + +