mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
feat: improve tiles export UX
This commit is contained in:
parent
72b6314d34
commit
7f587400ec
4 changed files with 102 additions and 86 deletions
10
index.html
10
index.html
|
|
@ -6032,7 +6032,7 @@
|
|||
</div>
|
||||
|
||||
<div id="exportToPngTilesScreen" style="display: none" class="dialog">
|
||||
<p>Map will be split into tiles and downloaded as a single zip file. Avoid saving to big images</p>
|
||||
<p>Map will be split into tiles and downloaded as a single zip file. Avoid saving too large images</p>
|
||||
<div data-tip="Number of columns" style="margin-bottom: 0.3em">
|
||||
<div class="label">Columns:</div>
|
||||
<input
|
||||
|
|
@ -6040,7 +6040,7 @@
|
|||
data-stored="tileCols"
|
||||
type="range"
|
||||
min="2"
|
||||
max="20"
|
||||
max="26"
|
||||
value="8"
|
||||
style="width: 10em"
|
||||
/>
|
||||
|
|
@ -6053,7 +6053,7 @@
|
|||
data-stored="tileRows"
|
||||
type="range"
|
||||
min="2"
|
||||
max="20"
|
||||
max="26"
|
||||
value="8"
|
||||
style="width: 10em"
|
||||
/>
|
||||
|
|
@ -6076,7 +6076,7 @@
|
|||
<div class="label">Total size:</div>
|
||||
<div id="tileSize" style="display: inline-block">1000 x 1000 px</div>
|
||||
</div>
|
||||
<div id="tileStatus" style="background-color: #33333310; font-style: italic"></div>
|
||||
<div id="tileStatus" style="font-style: italic"></div>
|
||||
</div>
|
||||
|
||||
<div id="resampleDialog" style="display: none" class="dialog">
|
||||
|
|
@ -8095,7 +8095,7 @@
|
|||
<script defer src="modules/io/save.js?v=1.96.00"></script>
|
||||
<script defer src="modules/io/load.js?v=1.97.02"></script>
|
||||
<script defer src="modules/io/cloud.js?v=1.96.00"></script>
|
||||
<script defer src="modules/io/export.js?v=1.96.00"></script>
|
||||
<script defer src="modules/io/export.js?v=1.97.03"></script>
|
||||
|
||||
<!-- Web Components -->
|
||||
<script defer src="components/fill-box.js"></script>
|
||||
|
|
|
|||
|
|
@ -71,70 +71,94 @@ async function exportToJpeg() {
|
|||
}
|
||||
|
||||
async function exportToPngTiles() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// download schema
|
||||
const urlSchema = await getMapURL("tiles", {debug: true, fullMap: true});
|
||||
await import("../../libs/jszip.min.js");
|
||||
const zip = new window.JSZip();
|
||||
const status = byId("tileStatus");
|
||||
status.innerHTML = "Preparing files...";
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = graphWidth;
|
||||
canvas.height = graphHeight;
|
||||
const urlSchema = await getMapURL("tiles", {debug: true, fullMap: true});
|
||||
await import("../../libs/jszip.min.js");
|
||||
const zip = new window.JSZip();
|
||||
|
||||
const imgSchema = new Image();
|
||||
imgSchema.src = urlSchema;
|
||||
imgSchema.onload = function () {
|
||||
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
|
||||
canvas.toBlob(blob => zip.file(`fmg_tile_schema.png`, blob));
|
||||
};
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
canvas.width = graphWidth;
|
||||
canvas.height = graphHeight;
|
||||
|
||||
// download tiles
|
||||
const url = await getMapURL("tiles", {fullMap: true});
|
||||
const tilesX = +document.getElementById("tileColsInput").value;
|
||||
const tilesY = +document.getElementById("tileRowsInput").value;
|
||||
const scale = +document.getElementById("tileScaleInput").value;
|
||||
const imgSchema = new Image();
|
||||
imgSchema.src = urlSchema;
|
||||
await loadImage(imgSchema);
|
||||
|
||||
const tileW = (graphWidth / tilesX) | 0;
|
||||
const tileH = (graphHeight / tilesY) | 0;
|
||||
const tolesTotal = tilesX * tilesY;
|
||||
status.innerHTML = "Drawing schema...";
|
||||
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height);
|
||||
const blob = await canvasToBlob(canvas, "image/png");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
zip.file("schema.png", blob);
|
||||
|
||||
const width = graphWidth * scale;
|
||||
const height = width * (tileH / tileW);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
// download tiles
|
||||
const url = await getMapURL("tiles", {fullMap: true});
|
||||
const tilesX = +byId("tileColsInput").value;
|
||||
const tilesY = +byId("tileRowsInput").value;
|
||||
const scale = +byId("tileScaleInput").value;
|
||||
const tolesTotal = tilesX * tilesY;
|
||||
|
||||
let loaded = 0;
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
|
||||
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
|
||||
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
|
||||
const name = `fmg_tile_${i}.png`;
|
||||
canvas.toBlob(blob => {
|
||||
zip.file(name, blob);
|
||||
loaded += 1;
|
||||
if (loaded === tolesTotal) return downloadZip();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
const tileW = (graphWidth / tilesX) | 0;
|
||||
const tileH = (graphHeight / tilesY) | 0;
|
||||
|
||||
function downloadZip() {
|
||||
const name = `${getFileName()}.zip`;
|
||||
zip.generateAsync({type: "blob"}).then(blob => {
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = name;
|
||||
link.click();
|
||||
link.remove();
|
||||
const width = graphWidth * scale;
|
||||
const height = width * (tileH / tileW);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
|
||||
resolve(true);
|
||||
});
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
await loadImage(img);
|
||||
|
||||
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
for (let y = 0, row = 0, id = 1; y + tileH <= graphHeight; y += tileH, row++) {
|
||||
const rowName = alphabet[row % alphabet.length];
|
||||
|
||||
for (let x = 0, cell = 1; x + tileW <= graphWidth; x += tileW, cell++, id++) {
|
||||
status.innerHTML = `Drawing tile ${rowName}${cell} (${id} of ${tolesTotal})...`;
|
||||
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
|
||||
const blob = await canvasToBlob(canvas, "image/png");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
zip.file(`${rowName}${cell}.png`, blob);
|
||||
}
|
||||
}
|
||||
|
||||
status.innerHTML = "Zipping files...";
|
||||
zip.generateAsync({type: "blob"}).then(blob => {
|
||||
status.innerHTML = "Downloading the archive...";
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = getFileName() + ".zip";
|
||||
link.click();
|
||||
link.remove();
|
||||
|
||||
status.innerHTML = 'Done. Check .zip file in "Downloads" (crtl + J)';
|
||||
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
|
||||
});
|
||||
|
||||
// promisified img.onload
|
||||
function loadImage(img) {
|
||||
return new Promise((resolve, reject) => {
|
||||
img.onload = () => resolve();
|
||||
img.onerror = err => reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
// promisified canvas.toBlob
|
||||
function canvasToBlob(canvas, mimeType, qualityArgument = 1) {
|
||||
return new Promise((resolve, reject) => {
|
||||
canvas.toBlob(
|
||||
blob => {
|
||||
if (blob) resolve(blob);
|
||||
else reject(new Error("Canvas toBlob() error"));
|
||||
},
|
||||
mimeType,
|
||||
qualityArgument
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// parse map svg to object url
|
||||
|
|
@ -148,14 +172,14 @@ async function getMapURL(type, options) {
|
|||
fullMap = false
|
||||
} = options || {};
|
||||
|
||||
const cloneEl = document.getElementById("map").cloneNode(true); // clone svg
|
||||
const cloneEl = byId("map").cloneNode(true); // clone svg
|
||||
cloneEl.id = "fantasyMap";
|
||||
document.body.appendChild(cloneEl);
|
||||
const clone = d3.select(cloneEl);
|
||||
if (!debug) clone.select("#debug")?.remove();
|
||||
|
||||
const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
|
||||
const svgDefs = document.getElementById("defElements");
|
||||
const svgDefs = byId("defElements");
|
||||
|
||||
const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
|
||||
if (isFirefox && type === "mesh") clone.select("#oceanPattern")?.remove();
|
||||
|
|
@ -218,7 +242,7 @@ async function getMapURL(type, options) {
|
|||
.forEach(el => {
|
||||
const href = el.getAttribute("href") || el.getAttribute("xlink:href");
|
||||
if (!href) return;
|
||||
const emblem = document.getElementById(href.slice(1));
|
||||
const emblem = byId(href.slice(1));
|
||||
if (emblem) cloneDefs.append(emblem.cloneNode(true));
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -867,11 +867,9 @@ byId("mapToLoad").addEventListener("change", function () {
|
|||
});
|
||||
|
||||
function openExportToPngTiles() {
|
||||
byId("tileStatus").innerHTML = "";
|
||||
closeDialogs();
|
||||
updateTilesOptions();
|
||||
const status = byId("tileStatus");
|
||||
status.innerHTML = "";
|
||||
let loading = null;
|
||||
|
||||
const inputs = byId("exportToPngTilesScreen").querySelectorAll("input");
|
||||
inputs.forEach(input => input.addEventListener("input", updateTilesOptions));
|
||||
|
|
@ -881,16 +879,7 @@ function openExportToPngTiles() {
|
|||
title: "Download tiles",
|
||||
width: "23em",
|
||||
buttons: {
|
||||
Download: function () {
|
||||
status.innerHTML = "Preparing for download...";
|
||||
setTimeout(() => (status.innerHTML = "Downloading. It may take some time."), 1000);
|
||||
loading = setInterval(() => (status.innerHTML += "."), 1000);
|
||||
exportToPngTiles().then(() => {
|
||||
clearInterval(loading);
|
||||
status.innerHTML = /* html */ `Done. Check file in "Downloads" (crtl + J)`;
|
||||
setTimeout(() => (status.innerHTML = ""), 8000);
|
||||
});
|
||||
},
|
||||
Download: () => exportToPngTiles(),
|
||||
Cancel: function () {
|
||||
$(this).dialog("close");
|
||||
}
|
||||
|
|
@ -898,7 +887,6 @@ function openExportToPngTiles() {
|
|||
close: () => {
|
||||
inputs.forEach(input => input.removeEventListener("input", updateTilesOptions));
|
||||
debug.selectAll("*").remove();
|
||||
clearInterval(loading);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -928,18 +916,22 @@ function updateTilesOptions() {
|
|||
const labels = [];
|
||||
const tileW = (graphWidth / tilesX) | 0;
|
||||
const tileH = (graphHeight / tilesY) | 0;
|
||||
for (let y = 0, i = 0; y + tileH <= graphHeight; y += tileH) {
|
||||
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
|
||||
|
||||
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
for (let y = 0, row = 0; y + tileH <= graphHeight; y += tileH, row++) {
|
||||
for (let x = 0, column = 1; x + tileW <= graphWidth; x += tileW, column++) {
|
||||
rects.push(`<rect x=${x} y=${y} width=${tileW} height=${tileH} />`);
|
||||
labels.push(`<text x=${x + tileW / 2} y=${y + tileH / 2}>${i}</text>`);
|
||||
const label = alphabet[row % alphabet.length] + column;
|
||||
labels.push(`<text x=${x + tileW / 2} y=${y + tileH / 2}>${label}</text>`);
|
||||
}
|
||||
}
|
||||
const rectsG = "<g fill='none' stroke='#000'>" + rects.join("") + "</g>";
|
||||
const labelsG =
|
||||
"<g fill='#000' stroke='none' text-anchor='middle' dominant-baseline='central' font-size='24px'>" +
|
||||
labels.join("") +
|
||||
"</g>";
|
||||
debug.html(rectsG + labelsG);
|
||||
|
||||
debug.html(`
|
||||
<g fill='none' stroke='#000'>${rects.join("")}</g>
|
||||
<g fill='#000' stroke='none' text-anchor='middle' dominant-baseline='central' font-size='18px'>${labels.join(
|
||||
""
|
||||
)}</g>
|
||||
`);
|
||||
}
|
||||
|
||||
// View mode
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
// version and caching control
|
||||
const version = "1.97.02"; // generator version, update each time
|
||||
const version = "1.97.03"; // generator version, update each time
|
||||
|
||||
{
|
||||
document.title += " v" + version;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue