mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 01:41:22 +01:00
fix some merge issues
This commit is contained in:
parent
7dc71a5616
commit
0db16b9a7e
6 changed files with 1789 additions and 1489 deletions
672
modules/load.js
672
modules/load.js
File diff suppressed because it is too large
Load diff
462
modules/save.js
462
modules/save.js
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
1000
modules/ui/style.js
1000
modules/ui/style.js
File diff suppressed because it is too large
Load diff
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue