diff --git a/index.css b/index.css index cb25d8c4..07f2ab8e 100644 --- a/index.css +++ b/index.css @@ -996,14 +996,26 @@ div.slider .ui-slider-handle { top: 100%; border: 1px solid #5e4fa2; background-color: #a4879b; - width: 44px; + width: 3.6em; } +#loadDropdown { + display: none; + position: absolute; + left: 53%; + top: 100%; + border: 1px solid #5e4fa2; + background-color: #a4879b; + width: 6em; +} + +#loadDropdown>div, #saveDropdown>div { padding: 2px 4px; cursor: pointer; } +#loadDropdown>div:hover, #saveDropdown>div:hover { color: white; } diff --git a/index.html b/index.html index a6f2333c..e5313fb5 100644 --- a/index.html +++ b/index.html @@ -1814,13 +1814,17 @@
- +
.map
.svg
.png
- + +
+
from URL
+
from file
+
@@ -2945,6 +2949,7 @@ + @@ -2969,7 +2974,6 @@ - diff --git a/main.js b/main.js index 97d5ffd8..3ca86481 100644 --- a/main.js +++ b/main.js @@ -81,9 +81,8 @@ population.append("g").attr("id", "urban"); fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%"); // assign events separately as not a viewbox child -scaleBar.on("mousemove", function() {tip("Click to open Units Editor")}); -legend.on("mousemove", function() {tip("Drag to change the position. Click to hide the legend")}) - .on("click", () => clearLegend()); +scaleBar.on("mousemove", () => tip("Click to open Units Editor")); +legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend()); // main data variables let grid = {}; // initial grapg based on jittered square grid and data @@ -111,61 +110,52 @@ landmass.append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr oceanPattern.append("rect").attr("fill", "url(#oceanic)").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight); oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight); -applyDefaultNamesData(); // apply default namesbase on load -applyDefaultStyle(); // apply style on load -generate(); // generate map on load -focusOn(); // based on searchParams focus on point, cell or burg from MFCG -applyPreset(); // apply saved layers preset on load +applyDefaultNamesData(); // always apply default namesbase load as namesdata is not stored in .map file -// show message on load if required -setTimeout(showWelcomeMessage, 7000); -function showWelcomeMessage() { - // Changelog dialog window - if (localStorage.getItem("version") != version) { - const link = 'https://www.reddit.com/r/FantasyMapGenerator/comments/cxu1c5/update_new_version_is_published_v_10'; // announcement on Reddit - alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version ${version}. +// load linked map from url or generate a random map +void function checkLoadParameters() { + const url = new URL(window.location.href); + const maplink = url.searchParams.get("maplink"); - This version is compatible with versions 0.8b and 0.9b, but not with older .map files. - Please use an archived version to open old files. - - - -

Join our Reddit community and - Discord server - to ask questions, share maps, discuss the Generator, report bugs and propose new features.

- -

Thanks for all supporters on Patreon!

`; - - $("#alert").dialog( - {resizable: false, title: "Fantasy Map Generator update", width: 310, - buttons: { - OK: function() { - localStorage.clear(); - localStorage.setItem("version", version); - $(this).dialog("close"); - } - }, - position: {my: "center", at: "center", of: "svg"} - }); + // check if URL is valid + if (maplink) { + const pattern = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; + const valid = pattern.test(maplink); + if (valid) {loadMapFromURL(maplink, 1); return;} else + showUploadErrorMessage("Map link is a valid URL", maplink); } + generateRandomMapOnLoad(); +}() + +function loadMapFromURL(maplink, random) { + const URL = decodeURIComponent(maplink); + + fetch(URL, {method: 'GET', mode: 'cors'}) + .then(response => { + if(response.ok) return response.blob(); + throw new Error("Cannot load map from URL"); + }).then(blob => uploadFile(blob)) + .catch(error => { + showUploadErrorMessage(error.message, URL, random); + if (random) generateRandomMapOnLoad(); + }); +} + +function showUploadErrorMessage(error, URL, random) { + console.log(error); + alertMessage.innerHTML = ` + Cannot load map from the link provided. + ${random?`A new random map is generated. `:''} + Please ensure the linked file is reachable`; + $("#alert").dialog({title: "Loading error", width: 320, buttons: {OK: function() {$(this).dialog("close");}}}); +} + +function generateRandomMapOnLoad() { + applyDefaultStyle(); // apply style + generate(); // generate map + focusOn(); // based on searchParams focus on point, cell or burg from MFCG + applyPreset(); // apply saved layers preset + setTimeout(showWelcomeMessage, 7000); // show welcome message if required } function applyDefaultNamesData() { @@ -431,11 +421,10 @@ function findBurgForMFCG(params) { const label = burgLabels.select("[data-id='" + b + "']"); if (label.size()) { - tip("Here stands the glorious city of " + burgs[b].name, true, "error"); + tip("Here stands the glorious city of " + burgs[b].name, true, "success", 10000); label.classed("drag", true).on("mouseover", function() { d3.select(this).classed("drag", false); label.on("mouseover", null); - tip("", true); }); } @@ -443,6 +432,55 @@ function findBurgForMFCG(params) { invokeActiveZooming(); } +function showWelcomeMessage() { + // Changelog dialog window + if (localStorage.getItem("version") != version) { + const link = 'https://www.reddit.com/r/FantasyMapGenerator/comments/cxu1c5/update_new_version_is_published_v_10'; // announcement on Reddit + alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version ${version}. + + This version is compatible with versions 0.8b and 0.9b, but not with older .map files. + Please use an archived version to open old files. + + + +

Join our Reddit community and + Discord server + to ask questions, share maps, discuss the Generator, report bugs and propose new features.

+ +

Thanks for all supporters on Patreon!

`; + + $("#alert").dialog( + {resizable: false, title: "Fantasy Map Generator update", width: 310, + buttons: { + OK: function() { + localStorage.clear(); + localStorage.setItem("version", version); + $(this).dialog("close"); + } + }, + position: {my: "center", at: "center", of: "svg"} + }); + } +} + function zoomed() { const transform = d3.event.transform; const scaleDiff = scale - transform.k; diff --git a/modules/burgs-and-states.js b/modules/burgs-and-states.js index f0478656..828b6354 100644 --- a/modules/burgs-and-states.js +++ b/modules/burgs-and-states.js @@ -265,6 +265,8 @@ const type = states[s].type; cells.c[n].forEach(function(e) { + if (cells.state[e] && e === states[cells.state[e]].center) return; // do not overwrite capital cells + const cultureCost = states[s].culture === cells.culture[e] ? -9 : 700; const biomeCost = getBiomeCost(cells.road[e], b, cells.biome[e], type); const heightCost = getHeightCost(pack.features[cells.f[e]], cells.h[e], type); @@ -278,7 +280,6 @@ if (cells.h[e] >= 20) cells.state[e] = s; // assign state to cell cost[e] = totalCost; queue.queue({e, p:totalCost, s, b}); - //const points = [cells.p[n][0], cells.p[n][1], (cells.p[n][0]+cells.p[e][0])/2, (cells.p[n][1]+cells.p[e][1])/2, cells.p[e][0], cells.p[e][1]]; //debug.append("text").attr("x", (cells.p[n][0]+cells.p[e][0])/2 - 1).attr("y", (cells.p[n][1]+cells.p[e][1])/2 - 1).text(rn(totalCost-p)).attr("font-size", .8); //debug.append("polyline").attr("points", points).attr("marker-mid", "url(#arrow)").attr("opacity", .6); diff --git a/modules/save-and-load.js b/modules/save-and-load.js index ff9d883d..6eca5740 100644 --- a/modules/save-and-load.js +++ b/modules/save-and-load.js @@ -210,6 +210,7 @@ function saveMap() { function uploadFile(file, callback) { console.time("loadMap"); + closeDialogs(); const fileReader = new FileReader(); fileReader.onload = function(fileLoadedEvent) { const dataLoaded = fileLoadedEvent.target.result; @@ -423,7 +424,7 @@ function parseLoadedData(data) { getCurrentPreset(); }() - void function restoreRulersEvents() { + void function restoreEvents() { ruler.selectAll("g").call(d3.drag().on("start", dragRuler)); ruler.selectAll("text").on("click", removeParent); ruler.selectAll("g.ruler circle").call(d3.drag().on("drag", dragRulerEdge)); @@ -431,6 +432,9 @@ function parseLoadedData(data) { ruler.selectAll("g.ruler rect").call(d3.drag().on("start", rulerCenterDrag)); ruler.selectAll("g.opisometer circle").call(d3.drag().on("start", dragOpisometerEnd)); ruler.selectAll("g.opisometer circle").call(d3.drag().on("start", dragOpisometerEnd)); + + scaleBar.on("mousemove", () => tip("Click to open Units Editor")); + legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend()); }() void function resolveVersionConflicts() { @@ -518,6 +522,7 @@ function parseLoadedData(data) { changeMapSize(); restoreDefaultEvents(); invokeActiveZooming(); - tip("Map is loaded"); + tip("Map is successfully loaded", true, "success", 7000); console.timeEnd("loadMap"); + showStatistics(); } diff --git a/modules/ui/burg-editor.js b/modules/ui/burg-editor.js index 7eadf7bc..0511dd14 100644 --- a/modules/ui/burg-editor.js +++ b/modules/ui/burg-editor.js @@ -94,7 +94,7 @@ function editBurg() { } function createNewGroup() { - if (!this.value) {tip("Please provide a valid group name"); return;} + if (!this.value) {tip("Please provide a valid group name", false, "error"); return;} let group = this.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, ""); if (Number.isFinite(+group.charAt(0))) group = "g" + group; diff --git a/modules/ui/burgs-editor.js b/modules/ui/burgs-editor.js index ffb45cb7..74e36189 100644 --- a/modules/ui/burgs-editor.js +++ b/modules/ui/burgs-editor.js @@ -186,7 +186,7 @@ function editBurgs() { if (!pack.burgs[burg].port) { const haven = pack.cells.haven[pack.burgs[burg].cell]; const port = haven ? pack.cells.f[haven] : -1; - if (!haven) tip("Port haven is not found, system won't be able to make a searoute", false, "warning"); + if (!haven) tip("Port haven is not found, system won't be able to make a searoute", false, "warn"); pack.burgs[burg].port = port; const g = pack.burgs[burg].capital ? "cities" : "towns"; diff --git a/modules/ui/general.js b/modules/ui/general.js index 77ce6fe4..9d6c21d6 100644 --- a/modules/ui/general.js +++ b/modules/ui/general.js @@ -18,16 +18,18 @@ const tooltip = document.getElementById("tooltip"); document.getElementById("dialogs").addEventListener("mousemove", showDataTip); document.getElementById("optionsContainer").addEventListener("mousemove", showDataTip); -function tip(tip = "Tip is undefined", main = false, error = false) { - const reg = "linear-gradient(0.1turn, #ffffff00, #5e5c5c66, #ffffff00)"; - const red = "linear-gradient(0.1turn, #ffffff00, #e3141499, #ffffff00)"; +function tip(tip = "Tip is undefined", main, error, time) { tooltip.innerHTML = tip; - tooltip.style.background = error ? red : reg; - if (main) tooltip.dataset.main = tip; + tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #5e5c5c80, #ffffff00)"; + if (error === "error") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #e11d1dcc, #ffffff00)"; else + if (error === "warn") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #be5d08cc, #ffffff00)"; else + if (error === "success") tooltip.style.background = "linear-gradient(0.1turn, #ffffff00, #127912cc, #ffffff00)"; + + if (main) tooltip.dataset.main = tip; // set main tip + if (time) setTimeout(tooltip.dataset.main = "", time); // clear main in some time } function showMainTip() { - tooltip.style.background = "linear-gradient(0.1turn, #aaffff00, #3a26264d, #ffffff00)"; tooltip.innerHTML = tooltip.dataset.main; } @@ -236,7 +238,8 @@ document.addEventListener("keydown", function(event) { else if (ctrl && key === 80) saveAsImage("png"); // Ctrl + "P" to save as PNG else if (ctrl && key === 83) saveAsImage("svg"); // Ctrl + "S" to save as SVG else if (ctrl && key === 77) saveMap(); // Ctrl + "M" to save MAP file - else if (ctrl && key === 76) mapToLoad.click(); // Ctrl + "L" to load MAP + else if (ctrl && key === 85) mapToLoad.click(); // Ctrl + "U" to load MAP from URL + else if (ctrl && key === 76) mapToLoad.click(); // Ctrl + "L" to load MAP from local file else if (key === 46) removeElementOnKey(); // "Delete" to remove the selected element else if (shift && key === 192) console.log(pack.cells); // Shift + "`" to log cells data diff --git a/modules/ui/options.js b/modules/ui/options.js index 2d6706b2..768f20c9 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -677,6 +677,7 @@ optionsContent.addEventListener("input", function(event) { else if (id === "provincesInput") provincesOutput.value = value; else if (id === "provincesOutput") provincesOutput.value = value; else if (id === "provincesOutput") powerOutput.value = value; + else if (id === "powerInput") powerOutput.value = value; else if (id === "powerOutput") powerInput.value = value; else if (id === "neutralInput") neutralOutput.value = value; else if (id === "neutralOutput") neutralInput.value = value; @@ -991,12 +992,14 @@ document.getElementById("sticked").addEventListener("click", function(event) { const id = event.target.id; if (id === "newMapButton") regeneratePrompt(); else if (id === "saveButton") toggleSavePane(); - else if (id === "loadMap") mapToLoad.click(); + else if (id === "loadButton") toggleLoadPane(); else if (id === "zoomReset") resetZoom(1000); else if (id === "saveMap") saveMap(); else if (id === "saveSVG") saveAsImage("svg"); else if (id === "savePNG") saveAsImage("png"); if (id === "saveMap" || id === "saveSVG" || id === "savePNG") toggleSavePane(); + if (id === "loadURL") {loadURL(); toggleLoadPane()} + if (id === "loadMap") {mapToLoad.click(); toggleLoadPane()} }); function regeneratePrompt() { @@ -1033,12 +1036,32 @@ function toggleSavePane() { } }); } +} +function toggleLoadPane() { + if (loadDropdown.style.display === "block") {loadDropdown.style.display = "none"; return;} + loadDropdown.style.display = "block"; +} + +function loadURL() { + const pattern = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; + const inner = `Provide URL to a .map file: `; + alertMessage.innerHTML = inner; + $("#alert").dialog({resizable: false, title: "Load map from URL", width: 280, + buttons: { + Load: function() { + const value = mapURL.value; + if (!pattern.test(value)) {tip("Please provide a valid URL", false, "error"); return;} + loadMapFromURL(value); + $(this).dialog("close"); + }, + Cancel: function() {$(this).dialog("close");} + } + }); } // load map document.getElementById("mapToLoad").addEventListener("change", function() { - closeDialogs(); const fileToLoad = this.files[0]; this.value = ""; uploadFile(fileToLoad);