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

1141
main.js

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,27 +1,27 @@
// Functions to save and load the map // Functions to save and load the map
"use strict"; 'use strict';
// download map as SVG // download map as SVG
async function saveSVG() { async function saveSVG() {
TIME && console.time("saveSVG"); TIME && console.time('saveSVG');
const url = await getMapURL("svg"); const url = await getMapURL('svg');
const link = document.createElement("a"); const link = document.createElement('a');
link.download = getFileName() + ".svg"; link.download = getFileName() + '.svg';
link.href = url; link.href = url;
link.click(); link.click();
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);
TIME && console.timeEnd("saveSVG"); TIME && console.timeEnd('saveSVG');
} }
// download map as PNG // download map as PNG
async function savePNG() { async function savePNG() {
TIME && console.time("savePNG"); TIME && console.time('savePNG');
const url = await getMapURL("png"); const url = await getMapURL('png');
const link = document.createElement("a"); const link = document.createElement('a');
const canvas = document.createElement("canvas"); const canvas = document.createElement('canvas');
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext('2d');
canvas.width = svgWidth * pngResolutionInput.value; canvas.width = svgWidth * pngResolutionInput.value;
canvas.height = svgHeight * pngResolutionInput.value; canvas.height = svgHeight * pngResolutionInput.value;
const img = new Image(); const img = new Image();
@ -29,56 +29,56 @@ async function savePNG() {
img.onload = function () { img.onload = function () {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
link.download = getFileName() + ".png"; link.download = getFileName() + '.png';
canvas.toBlob(function (blob) { canvas.toBlob(function (blob) {
link.href = window.URL.createObjectURL(blob); link.href = window.URL.createObjectURL(blob);
link.click(); link.click();
window.setTimeout(function () { window.setTimeout(function () {
canvas.remove(); canvas.remove();
window.URL.revokeObjectURL(link.href); 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); }, 1000);
}); });
}; };
TIME && console.timeEnd("savePNG"); TIME && console.timeEnd('savePNG');
} }
// download map as JPEG // download map as JPEG
async function saveJPEG() { async function saveJPEG() {
TIME && console.time("saveJPEG"); TIME && console.time('saveJPEG');
const url = await getMapURL("png"); const url = await getMapURL('png');
const canvas = document.createElement("canvas"); const canvas = document.createElement('canvas');
canvas.width = svgWidth * pngResolutionInput.value; canvas.width = svgWidth * pngResolutionInput.value;
canvas.height = svgHeight * pngResolutionInput.value; canvas.height = svgHeight * pngResolutionInput.value;
const img = new Image(); const img = new Image();
img.src = url; img.src = url;
img.onload = async function () { 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 quality = Math.min(rn(1 - pngResolutionInput.value / 20, 2), 0.92);
const URL = await canvas.toDataURL("image/jpeg", quality); const URL = await canvas.toDataURL('image/jpeg', quality);
const link = document.createElement("a"); const link = document.createElement('a');
link.download = getFileName() + ".jpeg"; link.download = getFileName() + '.jpeg';
link.href = URL; link.href = URL;
link.click(); 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); window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
}; };
TIME && console.timeEnd("saveJPEG"); TIME && console.timeEnd('saveJPEG');
} }
// download map as png tiles // download map as png tiles
async function saveTiles() { async function saveTiles() {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
// download schema // download schema
const urlSchema = await getMapURL("tiles", "schema"); const urlSchema = await getMapURL('tiles', 'schema');
const zip = new JSZip(); const zip = new JSZip();
const canvas = document.createElement("canvas"); const canvas = document.createElement('canvas');
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext('2d');
canvas.width = graphWidth; canvas.width = graphWidth;
canvas.height = graphHeight; canvas.height = graphHeight;
@ -86,14 +86,14 @@ async function saveTiles() {
imgSchema.src = urlSchema; imgSchema.src = urlSchema;
imgSchema.onload = function () { imgSchema.onload = function () {
ctx.drawImage(imgSchema, 0, 0, canvas.width, canvas.height); 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 // download tiles
const url = await getMapURL("tiles"); const url = await getMapURL('tiles');
const tilesX = +document.getElementById("tileColsInput").value; const tilesX = +document.getElementById('tileColsInput').value;
const tilesY = +document.getElementById("tileRowsInput").value; const tilesY = +document.getElementById('tileRowsInput').value;
const scale = +document.getElementById("tileScaleInput").value; const scale = +document.getElementById('tileScaleInput').value;
const tileW = (graphWidth / tilesX) | 0; const tileW = (graphWidth / tilesX) | 0;
const tileH = (graphHeight / tilesY) | 0; const tileH = (graphHeight / tilesY) | 0;
@ -112,7 +112,7 @@ async function saveTiles() {
for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) { for (let x = 0; x + tileW <= graphWidth; x += tileW, i++) {
ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height); ctx.drawImage(img, x, y, tileW, tileH, 0, 0, width, height);
const name = `fmg_tile_${i}.png`; const name = `fmg_tile_${i}.png`;
canvas.toBlob(blob => { canvas.toBlob((blob) => {
zip.file(name, blob); zip.file(name, blob);
loaded += 1; loaded += 1;
if (loaded === tolesTotal) return downloadZip(); if (loaded === tolesTotal) return downloadZip();
@ -123,8 +123,8 @@ async function saveTiles() {
function downloadZip() { function downloadZip() {
const name = `${getFileName()}.zip`; const name = `${getFileName()}.zip`;
zip.generateAsync({type: "blob"}).then(blob => { zip.generateAsync({type: 'blob'}).then((blob) => {
const link = document.createElement("a"); const link = document.createElement('a');
link.href = URL.createObjectURL(blob); link.href = URL.createObjectURL(blob);
link.download = name; link.download = name;
link.click(); link.click();
@ -139,43 +139,43 @@ async function saveTiles() {
// parse map svg to object url // parse map svg to object url
async function getMapURL(type, subtype) { async function getMapURL(type, subtype) {
const cloneEl = document.getElementById("map").cloneNode(true); // clone svg const cloneEl = document.getElementById('map').cloneNode(true); // clone svg
cloneEl.id = "fantasyMap"; cloneEl.id = 'fantasyMap';
document.body.appendChild(cloneEl); document.body.appendChild(cloneEl);
const clone = d3.select(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 cloneDefs = cloneEl.getElementsByTagName('defs')[0];
const svgDefs = document.getElementById("defElements"); const svgDefs = document.getElementById('defElements');
const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1; const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove(); if (isFirefox && type === 'mesh') clone.select('#oceanPattern').remove();
if (subtype === "globe") clone.select("#scaleBar").remove(); if (subtype === 'globe') clone.select('#scaleBar').remove();
if (subtype === "noWater") { if (subtype === 'noWater') {
clone.select("#oceanBase").attr("opacity", 0); clone.select('#oceanBase').attr('opacity', 0);
clone.select("#oceanPattern").attr("opacity", 0); clone.select('#oceanPattern').attr('opacity', 0);
} }
if (type !== "png") { if (type !== 'png') {
// reset transform to show the whole map // reset transform to show the whole map
clone.attr("width", graphWidth).attr("height", graphHeight); clone.attr('width', graphWidth).attr('height', graphHeight);
clone.select("#viewbox").attr("transform", null); clone.select('#viewbox').attr('transform', null);
} }
if (type === "svg") removeUnusedElements(clone); if (type === 'svg') removeUnusedElements(clone);
if (customization && type === "mesh") updateMeshCells(clone); if (customization && type === 'mesh') updateMeshCells(clone);
inlineStyle(clone); inlineStyle(clone);
// remove unused filters // remove unused filters
const filters = cloneEl.querySelectorAll("filter"); const filters = cloneEl.querySelectorAll('filter');
for (let i = 0; i < filters.length; i++) { for (let i = 0; i < filters.length; i++) {
const id = filters[i].id; const id = filters[i].id;
if (cloneEl.querySelector("[filter='url(#" + id + ")']")) continue; if (cloneEl.querySelector("[filter='url(#" + id + ")']")) continue;
if (cloneEl.getAttribute("filter") === "url(#" + id + ")") continue; if (cloneEl.getAttribute('filter') === 'url(#' + id + ')') continue;
filters[i].remove(); filters[i].remove();
} }
// remove unused patterns // remove unused patterns
const patterns = cloneEl.querySelectorAll("pattern"); const patterns = cloneEl.querySelectorAll('pattern');
for (let i = 0; i < patterns.length; i++) { for (let i = 0; i < patterns.length; i++) {
const id = patterns[i].id; const id = patterns[i].id;
if (cloneEl.querySelector("[fill='url(#" + id + ")']")) continue; if (cloneEl.querySelector("[fill='url(#" + id + ")']")) continue;
@ -183,7 +183,7 @@ async function getMapURL(type, subtype) {
} }
// remove unused symbols // remove unused symbols
const symbols = cloneEl.querySelectorAll("symbol"); const symbols = cloneEl.querySelectorAll('symbol');
for (let i = 0; i < symbols.length; i++) { for (let i = 0; i < symbols.length; i++) {
const id = symbols[i].id; const id = symbols[i].id;
if (cloneEl.querySelector("use[*|href='#" + id + "']")) continue; if (cloneEl.querySelector("use[*|href='#" + id + "']")) continue;
@ -191,42 +191,44 @@ async function getMapURL(type, subtype) {
} }
// add displayed emblems // add displayed emblems
if (layerIsOn("toggleEmblems") && emblems.selectAll("use").size()) { if (layerIsOn('toggleEmblems') && emblems.selectAll('use').size()) {
cloneEl cloneEl
.getElementById("emblems") .getElementById('emblems')
?.querySelectorAll("use") ?.querySelectorAll('use')
.forEach(el => { .forEach((el) => {
const href = el.getAttribute("href") || el.getAttribute("xlink:href"); const href = el.getAttribute('href') || el.getAttribute('xlink:href');
if (!href) return; if (!href) return;
const emblem = document.getElementById(href.slice(1)); const emblem = document.getElementById(href.slice(1));
if (emblem) cloneDefs.append(emblem.cloneNode(true)); if (emblem) cloneDefs.append(emblem.cloneNode(true));
}); });
} else { } else {
cloneDefs.querySelector("#defs-emblems")?.remove(); cloneDefs.querySelector('#defs-emblems')?.remove();
} }
// add resources TODO
// replace ocean pattern href to base64 // replace ocean pattern href to base64
if (PRODUCTION && cloneEl.getElementById("oceanicPattern")) { if (PRODUCTION && cloneEl.getElementById('oceanicPattern')) {
const el = cloneEl.getElementById("oceanicPattern"); const el = cloneEl.getElementById('oceanicPattern');
const url = el.getAttribute("href"); const url = el.getAttribute('href');
await new Promise(resolve => { await new Promise((resolve) => {
getBase64(url, base64 => { getBase64(url, (base64) => {
el.setAttribute("href", base64); el.setAttribute('href', base64);
resolve(); resolve();
}); });
}); });
} }
// add relief icons // add relief icons
if (cloneEl.getElementById("terrain")) { if (cloneEl.getElementById('terrain')) {
const uniqueElements = new Set(); const uniqueElements = new Set();
const terrainNodes = cloneEl.getElementById("terrain").childNodes; const terrainNodes = cloneEl.getElementById('terrain').childNodes;
for (let i = 0; i < terrainNodes.length; i++) { 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); uniqueElements.add(href);
} }
const defsRelief = svgDefs.getElementById("defs-relief"); const defsRelief = svgDefs.getElementById('defs-relief');
for (const terrain of [...uniqueElements]) { for (const terrain of [...uniqueElements]) {
const element = defsRelief.querySelector(terrain); const element = defsRelief.querySelector(terrain);
if (element) cloneDefs.appendChild(element.cloneNode(true)); if (element) cloneDefs.appendChild(element.cloneNode(true));
@ -234,47 +236,51 @@ async function getMapURL(type, subtype) {
} }
// add wind rose // add wind rose
if (cloneEl.getElementById("compass")) { if (cloneEl.getElementById('compass')) {
const rose = svgDefs.getElementById("rose"); const rose = svgDefs.getElementById('rose');
if (rose) cloneDefs.appendChild(rose.cloneNode(true)); if (rose) cloneDefs.appendChild(rose.cloneNode(true));
} }
// add port icon // add port icon
if (cloneEl.getElementById("anchors")) { if (cloneEl.getElementById('anchors')) {
const anchor = svgDefs.getElementById("icon-anchor"); const anchor = svgDefs.getElementById('icon-anchor');
if (anchor) cloneDefs.appendChild(anchor.cloneNode(true)); if (anchor) cloneDefs.appendChild(anchor.cloneNode(true));
} }
// add grid pattern // add grid pattern
if (cloneEl.getElementById("gridOverlay")?.hasChildNodes()) { if (cloneEl.getElementById('gridOverlay')?.hasChildNodes()) {
const type = cloneEl.getElementById("gridOverlay").getAttribute("type"); const type = cloneEl.getElementById('gridOverlay').getAttribute('type');
const pattern = svgDefs.getElementById("pattern_" + type); const pattern = svgDefs.getElementById('pattern_' + type);
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true)); if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
} }
if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching").remove(); // remove unused hatching group 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('fogging-cont')) cloneEl.getElementById('fog').remove(); // remove unused fog
if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths").remove(); // removed unused statePaths 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('labels')) cloneEl.getElementById('textPaths').remove(); // removed unused textPaths
// add armies style // 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 // add xlink: for href to support svg1.1
if (type === "svg") { if (type === 'svg') {
cloneEl.querySelectorAll("[href]").forEach(el => { cloneEl.querySelectorAll('[href]').forEach((el) => {
const href = el.getAttribute("href"); const href = el.getAttribute('href');
el.removeAttribute("href"); el.removeAttribute('href');
el.setAttribute("xlink:href", href); el.setAttribute('xlink:href', href);
}); });
} }
const fontStyle = await GFontToDataURI(getFontsToLoad(clone)); // load non-standard fonts 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(); clone.remove();
const serialized = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` + new XMLSerializer().serializeToString(cloneEl); 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); const url = window.URL.createObjectURL(blob);
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000); window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
return url; 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 // remove hidden g elements and g elements without children to make downloaded svg smaller in size
function removeUnusedElements(clone) { function removeUnusedElements(clone) {
if (!terrain.selectAll("use").size()) clone.select("#defs-relief").remove(); if (!terrain.selectAll('use').size()) clone.select('#defs-relief').remove();
if (markers.style("display") === "none") clone.select("#defs-markers").remove(); if (markers.style('display') === 'none') clone.select('#defs-markers').remove();
for (let empty = 1; empty; ) { for (let empty = 1; empty; ) {
empty = 0; empty = 0;
clone.selectAll("g").each(function () { clone.selectAll('g').each(function () {
if (!this.hasChildNodes() || this.style.display === "none" || this.classList.contains("hidden")) { if (!this.hasChildNodes() || this.style.display === 'none' || this.classList.contains('hidden')) {
empty++; empty++;
this.remove(); 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) { 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(); const scheme = getColorScheme();
clone.select("#heights").attr("filter", "url(#blur1)"); clone.select('#heights').attr('filter', 'url(#blur1)');
clone clone
.select("#heights") .select('#heights')
.selectAll("polygon") .selectAll('polygon')
.data(data) .data(data)
.join("polygon") .join('polygon')
.attr("points", d => getGridPolygon(d)) .attr('points', (d) => getGridPolygon(d))
.attr("id", d => "cell" + d) .attr('id', (d) => 'cell' + d)
.attr("stroke", d => getColor(grid.cells.h[d], scheme)); .attr('stroke', (d) => getColor(grid.cells.h[d], scheme));
} }
// for each g element get inline style // for each g element get inline style
function inlineStyle(clone) { function inlineStyle(clone) {
const emptyG = clone.append("g").node(); const emptyG = clone.append('g').node();
const defaultStyles = window.getComputedStyle(emptyG); 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); const compStyle = window.getComputedStyle(this);
let style = ""; let style = '';
for (let i = 0; i < compStyle.length; i++) { for (let i = 0; i < compStyle.length; i++) {
const key = compStyle[i]; const key = compStyle[i];
const value = compStyle.getPropertyValue(key); const value = compStyle.getPropertyValue(key);
// Firefox mask hack // Firefox mask hack
if (key === "mask-image" && value !== defaultStyles.getPropertyValue(key)) { if (key === 'mask-image' && value !== defaultStyles.getPropertyValue(key)) {
style += "mask-image: url('#land');"; style += "mask-image: url('#land');";
continue; 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 (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
if (value === defaultStyles.getPropertyValue(key)) continue; if (value === defaultStyles.getPropertyValue(key)) continue;
style += key + ":" + value + ";"; style += key + ':' + value + ';';
} }
for (const key in compStyle) { for (const key in compStyle) {
const value = compStyle.getPropertyValue(key); 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 (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
if (value === defaultStyles.getPropertyValue(key)) continue; 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(); emptyG.remove();
@ -353,35 +359,35 @@ function inlineStyle(clone) {
// get non-standard fonts used for labels to fetch them from web // get non-standard fonts used for labels to fetch them from web
function getFontsToLoad(clone) { 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 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; if (!this.hasChildNodes()) return;
const font = this.dataset.font; const font = this.dataset.font;
if (!font || webSafe.includes(font)) return; if (!font || webSafe.includes(font)) return;
fontsInUse.add(font); 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); if (legend.node().hasChildNodes() && !webSafe.includes(legendFont)) fontsInUse.add(legendFont);
const fonts = [...fontsInUse]; 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 // 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) { function GFontToDataURI(url) {
if (!url) return Promise.resolve(); if (!url) return Promise.resolve();
return fetch(url) // first fecth the embed stylesheet page return fetch(url) // first fecth the embed stylesheet page
.then(resp => resp.text()) // we only need the text of it .then((resp) => resp.text()) // we only need the text of it
.then(text => { .then((text) => {
let s = document.createElement("style"); let s = document.createElement('style');
s.innerHTML = text; s.innerHTML = text;
document.head.appendChild(s); 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 FontRule = (rule) => {
const src = rule.style.getPropertyValue("src"); const src = rule.style.getPropertyValue('src');
const url = src ? src.split("url(")[1].split(")")[0] : ""; const url = src ? src.split('url(')[1].split(')')[0] : '';
return {rule, src, url: url.substring(url.length - 1, 1)}; return {rule, src, url: url.substring(url.length - 1, 1)};
}; };
const fontProms = []; const fontProms = [];
@ -392,15 +398,15 @@ function GFontToDataURI(url) {
fontProms.push( fontProms.push(
fetch(fR.url) // fetch the actual font-file (.woff) fetch(fR.url) // fetch the actual font-file (.woff)
.then(resp => resp.blob()) .then((resp) => resp.blob())
.then(blob => { .then((blob) => {
return new Promise(resolve => { return new Promise((resolve) => {
let f = new FileReader(); let f = new FileReader();
f.onload = e => resolve(f.result); f.onload = (e) => resolve(f.result);
f.readAsDataURL(blob); 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 document.head.removeChild(s); // clean up
@ -410,29 +416,52 @@ function GFontToDataURI(url) {
// prepare map data for saving // prepare map data for saving
function getMapData() { function getMapData() {
TIME && console.time("createMapDataBlob"); TIME && console.time('createMapDataBlob');
return new Promise(resolve => { return new Promise((resolve) => {
const date = new Date(); const date = new Date();
const dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(); const dateString = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
const license = "File can be loaded in azgaar.github.io/Fantasy-Map-Generator"; const license = 'File can be loaded in azgaar.github.io/Fantasy-Map-Generator';
const params = [version, license, dateString, seed, graphWidth, graphHeight, mapId].join("|"); 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 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 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 notesData = JSON.stringify(notes);
const rulersString = rulers.toString(); const rulersString = rulers.toString();
// clone svg // clone svg
const cloneEl = document.getElementById("map").cloneNode(true); const cloneEl = document.getElementById('map').cloneNode(true);
// set transform values to default // set transform values to default
cloneEl.setAttribute("width", graphWidth); cloneEl.setAttribute('width', graphWidth);
cloneEl.setAttribute("height", graphHeight); cloneEl.setAttribute('height', graphHeight);
cloneEl.querySelector("#viewbox").removeAttribute("transform"); cloneEl.querySelector('#viewbox').removeAttribute('transform');
// always remove rulers // always remove rulers
cloneEl.querySelector("#ruler").innerHTML = ""; cloneEl.querySelector('#ruler').innerHTML = '';
const svg_xml = new XMLSerializer().serializeToString(cloneEl); const svg_xml = new XMLSerializer().serializeToString(cloneEl);
@ -444,53 +473,91 @@ function getMapData() {
const religions = JSON.stringify(pack.religions); const religions = JSON.stringify(pack.religions);
const provinces = JSON.stringify(pack.provinces); const provinces = JSON.stringify(pack.provinces);
const rivers = JSON.stringify(pack.rivers); const rivers = JSON.stringify(pack.rivers);
const resources = JSON.stringify(pack.resources);
// store name array only if it is not the same as default // store name array only if it is not the same as default
const defaultNB = Names.getNameBases(); const defaultNB = Names.getNameBases();
const namesData = nameBases const namesData = nameBases
.map((b, i) => { .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}`; return `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${names}`;
}) })
.join("/"); .join('/');
// round population to save resources // round population to save space
const pop = Array.from(pack.cells.pop).map(p => rn(p, 4)); const pop = Array.from(pack.cells.pop).map((p) => rn(p, 4));
// data format as below // 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 data = [
const blob = new Blob([data], {type: "text/plain"}); 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); resolve(blob);
}); });
} }
// Download .map file // Download .map file
async function saveMap() { async function saveMap() {
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); if (customization) return tip('Map cannot be saved when edit mode is active, please exit the mode and retry', false, 'error');
closeDialogs("#alert"); closeDialogs('#alert');
const blob = await getMapData(); const blob = await getMapData();
const URL = window.URL.createObjectURL(blob); const URL = window.URL.createObjectURL(blob);
const link = document.createElement("a"); const link = document.createElement('a');
link.download = getFileName() + ".map"; link.download = getFileName() + '.map';
link.href = URL; link.href = URL;
link.click(); 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); window.URL.revokeObjectURL(URL);
} }
function saveGeoJSON_Cells() { function saveGeoJSON_Cells() {
const json = {type: "FeatureCollection", features: []}; const json = {type: 'FeatureCollection', features: []};
const cells = pack.cells; const cells = pack.cells;
const getPopulation = i => { const getPopulation = (i) => {
const [r, u] = getCellPopulation(i); const [r, u] = getCellPopulation(i);
return rn(r + u); 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 coordinates = getCellCoordinates(cells.v[i]);
const height = getHeight(i); const height = getHeight(i);
const biome = cells.biome[i]; const biome = cells.biome[i];
@ -503,75 +570,75 @@ function saveGeoJSON_Cells() {
const neighbors = cells.c[i]; const neighbors = cells.c[i];
const properties = {id: i, height, biome, type, population, state, province, culture, religion, neighbors}; 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); json.features.push(feature);
}); });
const name = getFileName("Cells") + ".geojson"; const name = getFileName('Cells') + '.geojson';
downloadFile(JSON.stringify(json), name, "application/json"); downloadFile(JSON.stringify(json), name, 'application/json');
} }
function saveGeoJSON_Routes() { 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 coordinates = getRoutePoints(this);
const id = this.id; const id = this.id;
const type = this.parentElement.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); json.features.push(feature);
}); });
const name = getFileName("Routes") + ".geojson"; const name = getFileName('Routes') + '.geojson';
downloadFile(JSON.stringify(json), name, "application/json"); downloadFile(JSON.stringify(json), name, 'application/json');
} }
function saveGeoJSON_Rivers() { 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 coordinates = getRiverPoints(this);
const id = this.id; const id = this.id;
const width = +this.dataset.increment; const width = +this.dataset.increment;
const increment = +this.dataset.increment; const increment = +this.dataset.increment;
const river = pack.rivers.find(r => r.i === +id.slice(5)); const river = pack.rivers.find((r) => r.i === +id.slice(5));
const name = river ? river.name : ""; const name = river ? river.name : '';
const type = river ? river.type : ""; const type = river ? river.type : '';
const i = river ? river.i : ""; const i = river ? river.i : '';
const basin = river ? river.basin : ""; 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); json.features.push(feature);
}); });
const name = getFileName("Rivers") + ".geojson"; const name = getFileName('Rivers') + '.geojson';
downloadFile(JSON.stringify(json), name, "application/json"); downloadFile(JSON.stringify(json), name, 'application/json');
} }
function saveGeoJSON_Markers() { 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 coordinates = getQGIScoordinates(this.dataset.x, this.dataset.y);
const id = this.id; const id = this.id;
const type = this.dataset.id.substring(1); const type = this.dataset.id.substring(1);
const icon = document.getElementById(type).textContent; const icon = document.getElementById(type).textContent;
const note = notes.length ? notes.find(note => note.id === this.id) : null; const note = notes.length ? notes.find((note) => note.id === this.id) : null;
const name = note ? note.name : ""; const name = note ? note.name : '';
const legend = note ? note.legend : ""; 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); json.features.push(feature);
}); });
const name = getFileName("Markers") + ".geojson"; const name = getFileName('Markers') + '.geojson';
downloadFile(JSON.stringify(json), name, "application/json"); downloadFile(JSON.stringify(json), name, 'application/json');
} }
function getCellCoordinates(vertices) { function getCellCoordinates(vertices) {
const p = pack.vertices.p; 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]])]; return [coordinates.concat([coordinates[0]])];
} }
@ -601,21 +668,30 @@ function getRiverPoints(node) {
async function quickSave() { async function quickSave() {
if (customization) { 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; return;
} }
const blob = await getMapData(); const blob = await getMapData();
if (blob) ldb.set("lastMap", blob); // auto-save map 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); tip('Map is saved to browser memory. Please also save as .map file to secure progress', true, 'success', 2000);
} }
const saveReminder = function () { const saveReminder = function () {
if (localStorage.getItem("noReminder")) return; 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"]; 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(() => { saveReminder.reminder = setInterval(() => {
if (customization) return; if (customization) return;
tip(ra(message), true, "warn", 2500); tip(ra(message), true, 'warn', 2500);
}, 1e6); }, 1e6);
saveReminder.status = 1; saveReminder.status = 1;
}; };
@ -624,13 +700,13 @@ saveReminder();
function toggleSaveReminder() { function toggleSaveReminder() {
if (saveReminder.status) { 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); clearInterval(saveReminder.reminder);
localStorage.setItem("noReminder", true); localStorage.setItem('noReminder', true);
saveReminder.status = 0; saveReminder.status = 0;
} else { } else {
tip("Save reminder is turned on. Press CTRL+Q to turn off", true, "warn", 2000); tip('Save reminder is turned on. Press CTRL+Q to turn off', true, 'warn', 2000);
localStorage.removeItem("noReminder"); localStorage.removeItem('noReminder');
saveReminder(); saveReminder();
} }
} }

View file

@ -14,6 +14,7 @@ function getDefaultPresets() {
heightmap: ['toggleHeight', 'toggleRivers'], heightmap: ['toggleHeight', 'toggleRivers'],
physical: ['toggleCoordinates', 'toggleHeight', 'toggleIce', 'toggleRivers', 'toggleScaleBar'], physical: ['toggleCoordinates', 'toggleHeight', 'toggleIce', 'toggleRivers', 'toggleScaleBar'],
poi: ['toggleBorders', 'toggleHeight', 'toggleIce', 'toggleIcons', 'toggleMarkers', 'toggleRivers', 'toggleRoutes', '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'], military: ['toggleBorders', 'toggleIcons', 'toggleLabels', 'toggleMilitary', 'toggleRivers', 'toggleRoutes', 'toggleScaleBar', 'toggleStates'],
emblems: ['toggleBorders', 'toggleIcons', 'toggleIce', 'toggleEmblems', 'toggleRivers', 'toggleRoutes', 'toggleScaleBar', 'toggleStates'], emblems: ['toggleBorders', 'toggleIcons', 'toggleIce', 'toggleEmblems', 'toggleRivers', 'toggleRoutes', 'toggleScaleBar', 'toggleStates'],
landmass: ['toggleScaleBar'] 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 === 'editDiplomacyButton') editDiplomacy();
else if (button === 'editCulturesButton') editCultures(); else if (button === 'editCulturesButton') editCultures();
else if (button === 'editReligions') editReligions(); else if (button === 'editReligions') editReligions();
else if (button === 'editResources') editResources();
else if (button === 'editEmblemButton') openEmblemEditor(); else if (button === 'editEmblemButton') openEmblemEditor();
else if (button === 'editNamesBaseButton') editNamesbase(); else if (button === 'editNamesBaseButton') editNamesbase();
else if (button === 'editUnitsButton') editUnits(); else if (button === 'editUnitsButton') editUnits();
@ -83,6 +84,7 @@ function processFeatureRegeneration(event, button) {
else if (button === 'regenerateStates') regenerateStates(); else if (button === 'regenerateStates') regenerateStates();
else if (button === 'regenerateProvinces') regenerateProvinces(); else if (button === 'regenerateProvinces') regenerateProvinces();
else if (button === 'regenerateBurgs') regenerateBurgs(); else if (button === 'regenerateBurgs') regenerateBurgs();
else if (button === 'regenerateResources') regenerateResources();
else if (button === 'regenerateEmblems') regenerateEmblems(); else if (button === 'regenerateEmblems') regenerateEmblems();
else if (button === 'regenerateReligions') regenerateReligions(); else if (button === 'regenerateReligions') regenerateReligions();
else if (button === 'regenerateCultures') regenerateCultures(); else if (button === 'regenerateCultures') regenerateCultures();