fix some merge issues

This commit is contained in:
Azgaar 2021-07-05 23:21:02 +03:00
parent 7dc71a5616
commit 0db16b9a7e
6 changed files with 1789 additions and 1489 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,27 +1,27 @@
// Functions to save and load the map
"use strict";
'use strict';
// download map as SVG
async function saveSVG() {
TIME && console.time("saveSVG");
const url = await getMapURL("svg");
const link = document.createElement("a");
link.download = getFileName() + ".svg";
TIME && console.time('saveSVG');
const url = await getMapURL('svg');
const link = document.createElement('a');
link.download = getFileName() + '.svg';
link.href = url;
link.click();
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
TIME && console.timeEnd("saveSVG");
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, 'success', 5000);
TIME && console.timeEnd('saveSVG');
}
// download map as PNG
async function savePNG() {
TIME && console.time("savePNG");
const url = await getMapURL("png");
TIME && console.time('savePNG');
const url = await getMapURL('png');
const link = document.createElement("a");
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const link = document.createElement('a');
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = svgWidth * pngResolutionInput.value;
canvas.height = svgHeight * pngResolutionInput.value;
const img = new Image();
@ -29,56 +29,56 @@ async function savePNG() {
img.onload = function () {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
link.download = getFileName() + ".png";
link.download = getFileName() + '.png';
canvas.toBlob(function (blob) {
link.href = window.URL.createObjectURL(blob);
link.click();
window.setTimeout(function () {
canvas.remove();
window.URL.revokeObjectURL(link.href);
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, "success", 5000);
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`, true, 'success', 5000);
}, 1000);
});
};
TIME && console.timeEnd("savePNG");
TIME && console.timeEnd('savePNG');
}
// download map as JPEG
async function saveJPEG() {
TIME && console.time("saveJPEG");
const url = await getMapURL("png");
TIME && console.time('saveJPEG');
const url = await getMapURL('png');
const canvas = document.createElement("canvas");
const canvas = document.createElement('canvas');
canvas.width = svgWidth * pngResolutionInput.value;
canvas.height = svgHeight * pngResolutionInput.value;
const img = new Image();
img.src = url;
img.onload = async function () {
canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height);
canvas.getContext('2d').drawImage(img, 0, 0, canvas.width, canvas.height);
const quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), 0.92);
const URL = await canvas.toDataURL("image/jpeg", quality);
const link = document.createElement("a");
link.download = getFileName() + ".jpeg";
const URL = await canvas.toDataURL('image/jpeg', quality);
const link = document.createElement('a');
link.download = getFileName() + '.jpeg';
link.href = URL;
link.click();
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, 'success', 7000);
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
};
TIME && console.timeEnd("saveJPEG");
TIME && console.timeEnd('saveJPEG');
}
// download map as png tiles
async function saveTiles() {
return new Promise(async (resolve, reject) => {
// download schema
const urlSchema = await getMapURL("tiles", "schema");
const urlSchema = await getMapURL('tiles', 'schema');
const zip = new JSZip();
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = graphWidth;
canvas.height = graphHeight;
@ -86,14 +86,14 @@ async function saveTiles() {
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));
canvas.toBlob((blob) => zip.file(`fmg_tile_schema.png`, blob));
};
// download tiles
const url = await getMapURL("tiles");
const tilesX = +document.getElementById("tileColsInput").value;
const tilesY = +document.getElementById("tileRowsInput").value;
const scale = +document.getElementById("tileScaleInput").value;
const url = await getMapURL('tiles');
const tilesX = +document.getElementById('tileColsInput').value;
const tilesY = +document.getElementById('tileRowsInput').value;
const scale = +document.getElementById('tileScaleInput').value;
const tileW = (graphWidth / tilesX) | 0;
const tileH = (graphHeight / tilesY) | 0;
@ -112,7 +112,7 @@ async function saveTiles() {
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 => {
canvas.toBlob((blob) => {
zip.file(name, blob);
loaded += 1;
if (loaded === tolesTotal) return downloadZip();
@ -123,8 +123,8 @@ async function saveTiles() {
function downloadZip() {
const name = `${getFileName()}.zip`;
zip.generateAsync({type: "blob"}).then(blob => {
const link = document.createElement("a");
zip.generateAsync({type: 'blob'}).then((blob) => {
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = name;
link.click();
@ -139,43 +139,43 @@ async function saveTiles() {
// parse map svg to object url
async function getMapURL(type, subtype) {
const cloneEl = document.getElementById("map").cloneNode(true); // clone svg
cloneEl.id = "fantasyMap";
const cloneEl = document.getElementById('map').cloneNode(true); // clone svg
cloneEl.id = 'fantasyMap';
document.body.appendChild(cloneEl);
const clone = d3.select(cloneEl);
if (subtype !== "schema") clone.select("#debug").remove();
if (subtype !== 'schema') clone.select('#debug').remove();
const cloneDefs = cloneEl.getElementsByTagName("defs")[0];
const svgDefs = document.getElementById("defElements");
const cloneDefs = cloneEl.getElementsByTagName('defs')[0];
const svgDefs = document.getElementById('defElements');
const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove();
if (subtype === "globe") clone.select("#scaleBar").remove();
if (subtype === "noWater") {
clone.select("#oceanBase").attr("opacity", 0);
clone.select("#oceanPattern").attr("opacity", 0);
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
if (isFirefox && type === 'mesh') clone.select('#oceanPattern').remove();
if (subtype === 'globe') clone.select('#scaleBar').remove();
if (subtype === 'noWater') {
clone.select('#oceanBase').attr('opacity', 0);
clone.select('#oceanPattern').attr('opacity', 0);
}
if (type !== "png") {
if (type !== 'png') {
// reset transform to show the whole map
clone.attr("width", graphWidth).attr("height", graphHeight);
clone.select("#viewbox").attr("transform", null);
clone.attr('width', graphWidth).attr('height', graphHeight);
clone.select('#viewbox').attr('transform', null);
}
if (type === "svg") removeUnusedElements(clone);
if (customization && type === "mesh") updateMeshCells(clone);
if (type === 'svg') removeUnusedElements(clone);
if (customization && type === 'mesh') updateMeshCells(clone);
inlineStyle(clone);
// remove unused filters
const filters = cloneEl.querySelectorAll("filter");
const filters = cloneEl.querySelectorAll('filter');
for (let i = 0; i < filters.length; i++) {
const id = filters[i].id;
if (cloneEl.querySelector("[filter='url(#" + id + ")']")) continue;
if (cloneEl.getAttribute("filter") === "url(#" + id + ")") continue;
if (cloneEl.getAttribute('filter') === 'url(#' + id + ')') continue;
filters[i].remove();
}
// remove unused patterns
const patterns = cloneEl.querySelectorAll("pattern");
const patterns = cloneEl.querySelectorAll('pattern');
for (let i = 0; i < patterns.length; i++) {
const id = patterns[i].id;
if (cloneEl.querySelector("[fill='url(#" + id + ")']")) continue;
@ -183,7 +183,7 @@ async function getMapURL(type, subtype) {
}
// remove unused symbols
const symbols = cloneEl.querySelectorAll("symbol");
const symbols = cloneEl.querySelectorAll('symbol');
for (let i = 0; i < symbols.length; i++) {
const id = symbols[i].id;
if (cloneEl.querySelector("use[*|href='#" + id + "']")) continue;
@ -191,42 +191,44 @@ async function getMapURL(type, subtype) {
}
// add displayed emblems
if (layerIsOn("toggleEmblems") && emblems.selectAll("use").size()) {
if (layerIsOn('toggleEmblems') && emblems.selectAll('use').size()) {
cloneEl
.getElementById("emblems")
?.querySelectorAll("use")
.forEach(el => {
const href = el.getAttribute("href") || el.getAttribute("xlink:href");
.getElementById('emblems')
?.querySelectorAll('use')
.forEach((el) => {
const href = el.getAttribute('href') || el.getAttribute('xlink:href');
if (!href) return;
const emblem = document.getElementById(href.slice(1));
if (emblem) cloneDefs.append(emblem.cloneNode(true));
});
} else {
cloneDefs.querySelector("#defs-emblems")?.remove();
cloneDefs.querySelector('#defs-emblems')?.remove();
}
// add resources TODO
// replace ocean pattern href to base64
if (PRODUCTION && cloneEl.getElementById("oceanicPattern")) {
const el = cloneEl.getElementById("oceanicPattern");
const url = el.getAttribute("href");
await new Promise(resolve => {
getBase64(url, base64 => {
el.setAttribute("href", base64);
if (PRODUCTION && cloneEl.getElementById('oceanicPattern')) {
const el = cloneEl.getElementById('oceanicPattern');
const url = el.getAttribute('href');
await new Promise((resolve) => {
getBase64(url, (base64) => {
el.setAttribute('href', base64);
resolve();
});
});
}
// add relief icons
if (cloneEl.getElementById("terrain")) {
if (cloneEl.getElementById('terrain')) {
const uniqueElements = new Set();
const terrainNodes = cloneEl.getElementById("terrain").childNodes;
const terrainNodes = cloneEl.getElementById('terrain').childNodes;
for (let i = 0; i < terrainNodes.length; i++) {
const href = terrainNodes[i].getAttribute("href") || terrainNodes[i].getAttribute("xlink:href");
const href = terrainNodes[i].getAttribute('href') || terrainNodes[i].getAttribute('xlink:href');
uniqueElements.add(href);
}
const defsRelief = svgDefs.getElementById("defs-relief");
const defsRelief = svgDefs.getElementById('defs-relief');
for (const terrain of [...uniqueElements]) {
const element = defsRelief.querySelector(terrain);
if (element) cloneDefs.appendChild(element.cloneNode(true));
@ -234,47 +236,51 @@ async function getMapURL(type, subtype) {
}
// add wind rose
if (cloneEl.getElementById("compass")) {
const rose = svgDefs.getElementById("rose");
if (cloneEl.getElementById('compass')) {
const rose = svgDefs.getElementById('rose');
if (rose) cloneDefs.appendChild(rose.cloneNode(true));
}
// add port icon
if (cloneEl.getElementById("anchors")) {
const anchor = svgDefs.getElementById("icon-anchor");
if (cloneEl.getElementById('anchors')) {
const anchor = svgDefs.getElementById('icon-anchor');
if (anchor) cloneDefs.appendChild(anchor.cloneNode(true));
}
// add grid pattern
if (cloneEl.getElementById("gridOverlay")?.hasChildNodes()) {
const type = cloneEl.getElementById("gridOverlay").getAttribute("type");
const pattern = svgDefs.getElementById("pattern_" + type);
if (cloneEl.getElementById('gridOverlay')?.hasChildNodes()) {
const type = cloneEl.getElementById('gridOverlay').getAttribute('type');
const pattern = svgDefs.getElementById('pattern_' + type);
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
}
if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching").remove(); // remove unused hatching group
if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog").remove(); // remove unused fog
if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths").remove(); // removed unused statePaths
if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths").remove(); // removed unused textPaths
if (!cloneEl.getElementById('hatching').children.length) cloneEl.getElementById('hatching').remove(); // remove unused hatching group
if (!cloneEl.getElementById('fogging-cont')) cloneEl.getElementById('fog').remove(); // remove unused fog
if (!cloneEl.getElementById('regions')) cloneEl.getElementById('statePaths').remove(); // removed unused statePaths
if (!cloneEl.getElementById('labels')) cloneEl.getElementById('textPaths').remove(); // removed unused textPaths
// add armies style
if (cloneEl.getElementById("armies")) cloneEl.insertAdjacentHTML("afterbegin", "<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>");
if (cloneEl.getElementById('armies'))
cloneEl.insertAdjacentHTML(
'afterbegin',
'<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>'
);
// add xlink: for href to support svg1.1
if (type === "svg") {
cloneEl.querySelectorAll("[href]").forEach(el => {
const href = el.getAttribute("href");
el.removeAttribute("href");
el.setAttribute("xlink:href", href);
if (type === 'svg') {
cloneEl.querySelectorAll('[href]').forEach((el) => {
const href = el.getAttribute('href');
el.removeAttribute('href');
el.setAttribute('xlink:href', href);
});
}
const fontStyle = await GFontToDataURI(getFontsToLoad(clone)); // load non-standard fonts
if (fontStyle) clone.select("defs").append("style").text(fontStyle.join("\n")); // add font to style
if (fontStyle) clone.select('defs').append('style').text(fontStyle.join('\n')); // add font to style
clone.remove();
const serialized = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + new XMLSerializer().serializeToString(cloneEl);
const blob = new Blob([serialized], {type: "image/svg+xml;charset=utf-8"});
const blob = new Blob([serialized], {type: 'image/svg+xml;charset=utf-8'});
const url = window.URL.createObjectURL(blob);
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
return url;
@ -282,70 +288,70 @@ async function getMapURL(type, subtype) {
// remove hidden g elements and g elements without children to make downloaded svg smaller in size
function removeUnusedElements(clone) {
if (!terrain.selectAll("use").size()) clone.select("#defs-relief").remove();
if (markers.style("display") === "none") clone.select("#defs-markers").remove();
if (!terrain.selectAll('use').size()) clone.select('#defs-relief').remove();
if (markers.style('display') === 'none') clone.select('#defs-markers').remove();
for (let empty = 1; empty; ) {
empty = 0;
clone.selectAll("g").each(function () {
if (!this.hasChildNodes() || this.style.display === "none" || this.classList.contains("hidden")) {
clone.selectAll('g').each(function () {
if (!this.hasChildNodes() || this.style.display === 'none' || this.classList.contains('hidden')) {
empty++;
this.remove();
}
if (this.hasAttribute("display") && this.style.display === "inline") this.removeAttribute("display");
if (this.hasAttribute('display') && this.style.display === 'inline') this.removeAttribute('display');
});
}
}
function updateMeshCells(clone) {
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter(i => grid.cells.h[i] >= 20);
const data = renderOcean.checked ? grid.cells.i : grid.cells.i.filter((i) => grid.cells.h[i] >= 20);
const scheme = getColorScheme();
clone.select("#heights").attr("filter", "url(#blur1)");
clone.select('#heights').attr('filter', 'url(#blur1)');
clone
.select("#heights")
.selectAll("polygon")
.select('#heights')
.selectAll('polygon')
.data(data)
.join("polygon")
.attr("points", d => getGridPolygon(d))
.attr("id", d => "cell" + d)
.attr("stroke", d => getColor(grid.cells.h[d], scheme));
.join('polygon')
.attr('points', (d) => getGridPolygon(d))
.attr('id', (d) => 'cell' + d)
.attr('stroke', (d) => getColor(grid.cells.h[d], scheme));
}
// for each g element get inline style
function inlineStyle(clone) {
const emptyG = clone.append("g").node();
const emptyG = clone.append('g').node();
const defaultStyles = window.getComputedStyle(emptyG);
clone.selectAll("g, #ruler *, #scaleBar > text").each(function () {
clone.selectAll('g, #ruler *, #scaleBar > text').each(function () {
const compStyle = window.getComputedStyle(this);
let style = "";
let style = '';
for (let i = 0; i < compStyle.length; i++) {
const key = compStyle[i];
const value = compStyle.getPropertyValue(key);
// Firefox mask hack
if (key === "mask-image" && value !== defaultStyles.getPropertyValue(key)) {
if (key === 'mask-image' && value !== defaultStyles.getPropertyValue(key)) {
style += "mask-image: url('#land');";
continue;
}
if (key === "cursor") continue; // cursor should be default
if (key === 'cursor') continue; // cursor should be default
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
if (value === defaultStyles.getPropertyValue(key)) continue;
style += key + ":" + value + ";";
style += key + ':' + value + ';';
}
for (const key in compStyle) {
const value = compStyle.getPropertyValue(key);
if (key === "cursor") continue; // cursor should be default
if (key === 'cursor') continue; // cursor should be default
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
if (value === defaultStyles.getPropertyValue(key)) continue;
style += key + ":" + value + ";";
style += key + ':' + value + ';';
}
if (style != "") this.setAttribute("style", style);
if (style != '') this.setAttribute('style', style);
});
emptyG.remove();
@ -353,35 +359,35 @@ function inlineStyle(clone) {
// get non-standard fonts used for labels to fetch them from web
function getFontsToLoad(clone) {
const webSafe = ["Georgia", "Times+New+Roman", "Comic+Sans+MS", "Lucida+Sans+Unicode", "Courier+New", "Verdana", "Arial", "Impact"]; // fonts to not fetch
const webSafe = ['Georgia', 'Times+New+Roman', 'Comic+Sans+MS', 'Lucida+Sans+Unicode', 'Courier+New', 'Verdana', 'Arial', 'Impact']; // fonts to not fetch
const fontsInUse = new Set(); // to store fonts currently in use
clone.selectAll("#labels > g").each(function () {
clone.selectAll('#labels > g').each(function () {
if (!this.hasChildNodes()) return;
const font = this.dataset.font;
if (!font || webSafe.includes(font)) return;
fontsInUse.add(font);
});
const legendFont = legend.attr("data-font");
const legendFont = legend.attr('data-font');
if (legend.node().hasChildNodes() && !webSafe.includes(legendFont)) fontsInUse.add(legendFont);
const fonts = [...fontsInUse];
return fonts.length ? "https://fonts.googleapis.com/css?family=" + fonts.join("|") : null;
return fonts.length ? 'https://fonts.googleapis.com/css?family=' + fonts.join('|') : null;
}
// code from Kaiido's answer https://stackoverflow.com/questions/42402584/how-to-use-google-fonts-in-canvas-when-drawing-dom-objects-in-svg
function GFontToDataURI(url) {
if (!url) return Promise.resolve();
return fetch(url) // first fecth the embed stylesheet page
.then(resp => resp.text()) // we only need the text of it
.then(text => {
let s = document.createElement("style");
.then((resp) => resp.text()) // we only need the text of it
.then((text) => {
let s = document.createElement('style');
s.innerHTML = text;
document.head.appendChild(s);
const styleSheet = Array.prototype.filter.call(document.styleSheets, sS => sS.ownerNode === s)[0];
const styleSheet = Array.prototype.filter.call(document.styleSheets, (sS) => sS.ownerNode === s)[0];
const FontRule = rule => {
const src = rule.style.getPropertyValue("src");
const url = src ? src.split("url(")[1].split(")")[0] : "";
const FontRule = (rule) => {
const src = rule.style.getPropertyValue('src');
const url = src ? src.split('url(')[1].split(')')[0] : '';
return {rule, src, url: url.substring(url.length - 1, 1)};
};
const fontProms = [];
@ -392,15 +398,15 @@ function GFontToDataURI(url) {
fontProms.push(
fetch(fR.url) // fetch the actual font-file (.woff)
.then(resp => resp.blob())
.then(blob => {
return new Promise(resolve => {
.then((resp) => resp.blob())
.then((blob) => {
return new Promise((resolve) => {
let f = new FileReader();
f.onload = e => resolve(f.result);
f.onload = (e) => resolve(f.result);
f.readAsDataURL(blob);
});
})
.then(dataURL => fR.rule.cssText.replace(fR.url, dataURL))
.then((dataURL) => fR.rule.cssText.replace(fR.url, dataURL))
);
}
document.head.removeChild(s); // clean up
@ -410,29 +416,52 @@ function GFontToDataURI(url) {
// prepare map data for saving
function getMapData() {
TIME && console.time("createMapDataBlob");
TIME && console.time('createMapDataBlob');
return new Promise(resolve => {
return new Promise((resolve) => {
const date = new Date();
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator";
const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join("|");
const settings = [distanceUnitInput.value, distanceScaleInput.value, areaUnit.value, heightUnit.value, heightExponentInput.value, temperatureScale.value, barSizeInput.value, barLabel.value, barBackOpacity.value, barBackColor.value, barPosX.value, barPosY.value, populationRate, urbanization, mapSizeOutput.value, latitudeOutput.value, temperatureEquatorOutput.value, temperaturePoleOutput.value, precOutput.value, JSON.stringify(options), mapName.value, +hideLabels.checked].join("|");
const dateString = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
const license = 'File can be loaded in azgaar.github.io/Fantasy-Map-Generator';
const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join('|');
const settings = [
distanceUnitInput.value,
distanceScaleInput.value,
areaUnit.value,
heightUnit.value,
heightExponentInput.value,
temperatureScale.value,
barSizeInput.value,
barLabel.value,
barBackOpacity.value,
barBackColor.value,
barPosX.value,
barPosY.value,
populationRate,
urbanization,
mapSizeOutput.value,
latitudeOutput.value,
temperatureEquatorOutput.value,
temperaturePoleOutput.value,
precOutput.value,
JSON.stringify(options),
mapName.value,
+hideLabels.checked
].join('|');
const coords = JSON.stringify(mapCoordinates);
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join("|");
const biomes = [biomesData.color, biomesData.habitability, biomesData.name].join('|');
const notesData = JSON.stringify(notes);
const rulersString = rulers.toString();
// clone svg
const cloneEl = document.getElementById("map").cloneNode(true);
const cloneEl = document.getElementById('map').cloneNode(true);
// set transform values to default
cloneEl.setAttribute("width", graphWidth);
cloneEl.setAttribute("height", graphHeight);
cloneEl.querySelector("#viewbox").removeAttribute("transform");
cloneEl.setAttribute('width', graphWidth);
cloneEl.setAttribute('height', graphHeight);
cloneEl.querySelector('#viewbox').removeAttribute('transform');
// always remove rulers
cloneEl.querySelector("#ruler").innerHTML = "";
cloneEl.querySelector('#ruler').innerHTML = '';
const svg_xml = new XMLSerializer().serializeToString(cloneEl);
@ -444,53 +473,91 @@ function getMapData() {
const religions = JSON.stringify(pack.religions);
const provinces = JSON.stringify(pack.provinces);
const rivers = JSON.stringify(pack.rivers);
const resources = JSON.stringify(pack.resources);
// store name array only if it is not the same as default
const defaultNB = Names.getNameBases();
const namesData = nameBases
.map((b, i) => {
const names = defaultNB[i] && defaultNB[i].b === b.b ? "" : b.b;
const names = defaultNB[i] && defaultNB[i].b === b.b ? '' : b.b;
return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`;
})
.join("/");
.join('/');
// round population to save resources
const pop = Array.from(pack.cells.pop).map(p => rn(p, 4));
// round population to save space
const pop = Array.from(pack.cells.pop).map((p) => rn(p, 4));
// data format as below
const data = [params, settings, coords, biomes, notesData, svg_xml, gridGeneral, grid.cells.h, grid.cells.prec, grid.cells.f, grid.cells.t, grid.cells.temp, features, cultures, states, burgs, pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl, pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state, pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces, namesData, rivers, rulersString].join("\r\n");
const blob = new Blob([data], {type: "text/plain"});
const data = [
params,
settings,
coords,
biomes,
notesData,
svg_xml,
gridGeneral,
grid.cells.h,
grid.cells.prec,
grid.cells.f,
grid.cells.t,
grid.cells.temp,
features,
cultures,
states,
burgs,
pack.cells.biome,
pack.cells.burg,
pack.cells.conf,
pack.cells.culture,
pack.cells.fl,
pop,
pack.cells.r,
pack.cells.road,
pack.cells.s,
pack.cells.state,
pack.cells.religion,
pack.cells.province,
pack.cells.crossroad,
religions,
provinces,
namesData,
rivers,
rulersString,
pack.cells.resource,
resources
].join('\r\n');
const blob = new Blob([data], {type: 'text/plain'});
TIME && console.timeEnd("createMapDataBlob");
TIME && console.timeEnd('createMapDataBlob');
resolve(blob);
});
}
// Download .map file
async function saveMap() {
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
closeDialogs("#alert");
if (customization) return tip('Map cannot be saved when edit mode is active, please exit the mode and retry', false, 'error');
closeDialogs('#alert');
const blob = await getMapData();
const URL = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.download = getFileName() + ".map";
const link = document.createElement('a');
link.download = getFileName() + '.map';
link.href = URL;
link.click();
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, 'success', 7000);
window.URL.revokeObjectURL(URL);
}
function saveGeoJSON_Cells() {
const json = {type: "FeatureCollection", features: []};
const json = {type: 'FeatureCollection', features: []};
const cells = pack.cells;
const getPopulation = i => {
const getPopulation = (i) => {
const [r, u] = getCellPopulation(i);
return rn(r + u);
};
const getHeight = i => parseInt(getFriendlyHeight([cells.p[i][0], cells.p[i][1]]));
const getHeight = (i) => parseInt(getFriendlyHeight([cells.p[i][0], cells.p[i][1]]));
cells.i.forEach(i => {
cells.i.forEach((i) => {
const coordinates = getCellCoordinates(cells.v[i]);
const height = getHeight(i);
const biome = cells.biome[i];
@ -503,75 +570,75 @@ function saveGeoJSON_Cells() {
const neighbors = cells.c[i];
const properties = {id: i, height, biome, type, population, state, province, culture, religion, neighbors};
const feature = {type: "Feature", geometry: {type: "Polygon", coordinates}, properties};
const feature = {type: 'Feature', geometry: {type: 'Polygon', coordinates}, properties};
json.features.push(feature);
});
const name = getFileName("Cells") + ".geojson";
downloadFile(JSON.stringify(json), name, "application/json");
const name = getFileName('Cells') + '.geojson';
downloadFile(JSON.stringify(json), name, 'application/json');
}
function saveGeoJSON_Routes() {
const json = {type: "FeatureCollection", features: []};
const json = {type: 'FeatureCollection', features: []};
routes.selectAll("g > path").each(function () {
routes.selectAll('g > path').each(function () {
const coordinates = getRoutePoints(this);
const id = this.id;
const type = this.parentElement.id;
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, type}};
const feature = {type: 'Feature', geometry: {type: 'LineString', coordinates}, properties: {id, type}};
json.features.push(feature);
});
const name = getFileName("Routes") + ".geojson";
downloadFile(JSON.stringify(json), name, "application/json");
const name = getFileName('Routes') + '.geojson';
downloadFile(JSON.stringify(json), name, 'application/json');
}
function saveGeoJSON_Rivers() {
const json = {type: "FeatureCollection", features: []};
const json = {type: 'FeatureCollection', features: []};
rivers.selectAll("path").each(function () {
rivers.selectAll('path').each(function () {
const coordinates = getRiverPoints(this);
const id = this.id;
const width = +this.dataset.increment;
const increment = +this.dataset.increment;
const river = pack.rivers.find(r => r.i === +id.slice(5));
const name = river ? river.name : "";
const type = river ? river.type : "";
const i = river ? river.i : "";
const basin = river ? river.basin : "";
const river = pack.rivers.find((r) => r.i === +id.slice(5));
const name = river ? river.name : '';
const type = river ? river.type : '';
const i = river ? river.i : '';
const basin = river ? river.basin : '';
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, i, basin, name, type, width, increment}};
const feature = {type: 'Feature', geometry: {type: 'LineString', coordinates}, properties: {id, i, basin, name, type, width, increment}};
json.features.push(feature);
});
const name = getFileName("Rivers") + ".geojson";
downloadFile(JSON.stringify(json), name, "application/json");
const name = getFileName('Rivers') + '.geojson';
downloadFile(JSON.stringify(json), name, 'application/json');
}
function saveGeoJSON_Markers() {
const json = {type: "FeatureCollection", features: []};
const json = {type: 'FeatureCollection', features: []};
markers.selectAll("use").each(function () {
markers.selectAll('use').each(function () {
const coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y);
const id = this.id;
const type = this.dataset.id.substring(1);
const icon = document.getElementById(type).textContent;
const note = notes.length ? notes.find(note => note.id === this.id) : null;
const name = note ? note.name : "";
const legend = note ? note.legend : "";
const note = notes.length ? notes.find((note) => note.id === this.id) : null;
const name = note ? note.name : '';
const legend = note ? note.legend : '';
const feature = {type: "Feature", geometry: {type: "Point", coordinates}, properties: {id, type, icon, name, legend}};
const feature = {type: 'Feature', geometry: {type: 'Point', coordinates}, properties: {id, type, icon, name, legend}};
json.features.push(feature);
});
const name = getFileName("Markers") + ".geojson";
downloadFile(JSON.stringify(json), name, "application/json");
const name = getFileName('Markers') + '.geojson';
downloadFile(JSON.stringify(json), name, 'application/json');
}
function getCellCoordinates(vertices) {
const p = pack.vertices.p;
const coordinates = vertices.map(n => getQGIScoordinates(p[n][0], p[n][1]));
const coordinates = vertices.map((n) => getQGIScoordinates(p[n][0], p[n][1]));
return [coordinates.concat([coordinates[0]])];
}
@ -601,21 +668,30 @@ function getRiverPoints(node) {
async function quickSave() {
if (customization) {
tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
tip('Map cannot be saved when edit mode is active, please exit the mode and retry', false, 'error');
return;
}
const blob = await getMapData();
if (blob) ldb.set("lastMap", blob); // auto-save map
tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 2000);
if (blob) ldb.set('lastMap', blob); // auto-save map
tip('Map is saved to browser memory. Please also save as .map file to secure progress', true, 'success', 2000);
}
const saveReminder = function () {
if (localStorage.getItem("noReminder")) return;
const message = ["Please don't forget to save your work as a .map file", "Please remember to save work as a .map file", "Saving in .map format will ensure your data won't be lost in case of issues", "Safety is number one priority. Please save the map", "Don't forget to save your map on a regular basis!", "Just a gentle reminder for you to save the map", "Please don't forget to save your progress (saving as .map is the best option)", "Don't want to be reminded about need to save? Press CTRL+Q"];
if (localStorage.getItem('noReminder')) return;
const message = [
"Please don't forget to save your work as a .map file",
'Please remember to save work as a .map file',
"Saving in .map format will ensure your data won't be lost in case of issues",
'Safety is number one priority. Please save the map',
"Don't forget to save your map on a regular basis!",
'Just a gentle reminder for you to save the map',
"Please don't forget to save your progress (saving as .map is the best option)",
"Don't want to be reminded about need to save? Press CTRL+Q"
];
saveReminder.reminder = setInterval(() => {
if (customization) return;
tip(ra(message), true, "warn", 2500);
tip(ra(message), true, 'warn', 2500);
}, 1e6);
saveReminder.status = 1;
};
@ -624,13 +700,13 @@ saveReminder();
function toggleSaveReminder() {
if (saveReminder.status) {
tip("Save reminder is turned off. Press CTRL+Q again to re-initiate", true, "warn", 2000);
tip('Save reminder is turned off. Press CTRL+Q again to re-initiate', true, 'warn', 2000);
clearInterval(saveReminder.reminder);
localStorage.setItem("noReminder", true);
localStorage.setItem('noReminder', true);
saveReminder.status = 0;
} else {
tip("Save reminder is turned on. Press CTRL+Q to turn off", true, "warn", 2000);
localStorage.removeItem("noReminder");
tip('Save reminder is turned on. Press CTRL+Q to turn off', true, 'warn', 2000);
localStorage.removeItem('noReminder');
saveReminder();
}
}

View file

@ -14,6 +14,7 @@ function getDefaultPresets() {
heightmap: ['toggleHeight', 'toggleRivers'],
physical: ['toggleCoordinates', 'toggleHeight', 'toggleIce', 'toggleRivers', 'toggleScaleBar'],
poi: ['toggleBorders', 'toggleHeight', 'toggleIce', 'toggleIcons', 'toggleMarkers', 'toggleRivers', 'toggleRoutes', 'toggleScaleBar'],
economical: ['toggleResources', 'toggleBiomes', 'toggleBorders', 'toggleIcons', 'toggleIce', 'toggleLabels', 'toggleRivers', 'toggleRoutes', 'toggleScaleBar'],
military: ['toggleBorders', 'toggleIcons', 'toggleLabels', 'toggleMilitary', 'toggleRivers', 'toggleRoutes', 'toggleScaleBar', 'toggleStates'],
emblems: ['toggleBorders', 'toggleIcons', 'toggleIce', 'toggleEmblems', 'toggleRivers', 'toggleRoutes', 'toggleScaleBar', 'toggleStates'],
landmass: ['toggleScaleBar']

File diff suppressed because it is too large Load diff

View file

@ -17,6 +17,7 @@ toolsContent.addEventListener('click', function (event) {
else if (button === 'editDiplomacyButton') editDiplomacy();
else if (button === 'editCulturesButton') editCultures();
else if (button === 'editReligions') editReligions();
else if (button === 'editResources') editResources();
else if (button === 'editEmblemButton') openEmblemEditor();
else if (button === 'editNamesBaseButton') editNamesbase();
else if (button === 'editUnitsButton') editUnits();
@ -83,6 +84,7 @@ function processFeatureRegeneration(event, button) {
else if (button === 'regenerateStates') regenerateStates();
else if (button === 'regenerateProvinces') regenerateProvinces();
else if (button === 'regenerateBurgs') regenerateBurgs();
else if (button === 'regenerateResources') regenerateResources();
else if (button === 'regenerateEmblems') regenerateEmblems();
else if (button === 'regenerateReligions') regenerateReligions();
else if (button === 'regenerateCultures') regenerateCultures();