mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-21 19:41:23 +01:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
43e791a835
13 changed files with 971 additions and 583 deletions
30
index.css
30
index.css
|
|
@ -996,14 +996,26 @@ div.slider .ui-slider-handle {
|
|||
top: 100%;
|
||||
border: 1px solid #5e4fa2;
|
||||
background-color: #a4879b;
|
||||
width: 44px;
|
||||
width: 5em;
|
||||
}
|
||||
|
||||
#loadDropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 53%;
|
||||
top: 100%;
|
||||
border: 1px solid #5e4fa2;
|
||||
background-color: #a4879b;
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
#loadDropdown>div,
|
||||
#saveDropdown>div {
|
||||
padding: 2px 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#loadDropdown>div:hover,
|
||||
#saveDropdown>div:hover {
|
||||
color: white;
|
||||
}
|
||||
|
|
@ -1498,6 +1510,7 @@ div.states > div.biomeArea {
|
|||
cursor: default;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#title_name {
|
||||
|
|
@ -1796,6 +1809,12 @@ svg.button {
|
|||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#info-line {
|
||||
font-size: .9em;
|
||||
color: gray;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.optionsSeedRestore {
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
|
|
@ -1910,6 +1929,15 @@ svg.button {
|
|||
margin: 10px 0 0 7px;
|
||||
}
|
||||
|
||||
#errorBox {
|
||||
font-size: .9em;
|
||||
font-family: monospace;
|
||||
color: #920303;
|
||||
background-color: #dabdbd91;
|
||||
padding: 2px;
|
||||
border: 1px solid #916e7f;
|
||||
}
|
||||
|
||||
#debug {
|
||||
font-size: 1px;
|
||||
opacity: 0.8;
|
||||
|
|
|
|||
72
index.html
72
index.html
|
|
@ -882,7 +882,7 @@
|
|||
|
||||
<div id="collapsible">
|
||||
<button id="optionsTrigger" data-tip="Click to show options pane. Shortcut: Tab" class="options glow" onclick="showOptions(event)" style="padding:7px 5px">►</button>
|
||||
<button id="regenerate" data-tip="Click to generate a new map. Shortcut: F7" onclick="regeneratePrompt()" class="options" style="display:none; padding:7px 8px">New Map!</button>
|
||||
<button id="regenerate" data-tip="Click to generate a new map. Shortcut: F2" onclick="regeneratePrompt()" class="options" style="display:none; padding:7px 8px">New Map!</button>
|
||||
</div>
|
||||
|
||||
<div id="options" style="display:none">
|
||||
|
|
@ -909,7 +909,8 @@
|
|||
<option value="landmass">Pure landmass</option>
|
||||
<option hidden value="custom">Custom (not saved)</option>
|
||||
</select>
|
||||
<button id="savePreset" data-tip="Click to save displayed layers as a new preset" class="icon-plus styleButton" onclick="savePreset()"></button>
|
||||
<button id="savePresetButton" data-tip="Click to save displayed layers as a new preset" class="icon-plus styleButton" style="display:none" onclick="savePreset()"></button>
|
||||
<button id="removePresetButton" data-tip="Click to remove current custom preset" class="icon-minus styleButton" style="display:none" onclick="removePreset()"></button>
|
||||
|
||||
<p data-tip="Click to toggle a layer, drag to raise or lower a layer">Displayed layers:</p>
|
||||
<ul data-tip="Click to toggle a layer, drag to raise or lower a layer" id="mapLayers">
|
||||
|
|
@ -1470,7 +1471,7 @@
|
|||
</div>
|
||||
|
||||
<div id="optionsContent" class="tabcontent">
|
||||
<p data-tip="Map generation settings. Generate a new map to apply the settings">Map Generation (new map to apply):</p>
|
||||
<p data-tip="Map generation settings. Generate a new map to apply the settings">Map settings (new map to apply):</p>
|
||||
<table>
|
||||
|
||||
<tr data-tip="Map height and width in pixels. Please consider reducing map size in case of performance issues">
|
||||
|
|
@ -1563,7 +1564,7 @@
|
|||
<td>
|
||||
<i data-locked=0 id="lock_provinces" class="icon-lock-open"></i>
|
||||
</td>
|
||||
<td>Provinces number</td>
|
||||
<td>Provinces ratio</td>
|
||||
<td>
|
||||
<input id="provincesInput" data-stored="provinces" type="range" min=0 max=100 value=30>
|
||||
</td>
|
||||
|
|
@ -1585,7 +1586,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Define state and cultures growth rate. Defines how many lands will stay neutral">
|
||||
<tr data-tip="Set state and cultures growth rate. Defines how many lands will stay neutral">
|
||||
<td>
|
||||
<i data-locked=0 id="lock_neutral" class="icon-lock-open"></i>
|
||||
</td>
|
||||
|
|
@ -1598,7 +1599,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Define how many towns (non-capital burgs) should be generated">
|
||||
<tr data-tip="Define a number of towns to be placed (if suitable area is enougth)">
|
||||
<td>
|
||||
<i data-locked=0 id="lock_manors" class="icon-lock-open"></i>
|
||||
</td>
|
||||
|
|
@ -1611,7 +1612,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Define how many organized religions and cults should be generated">
|
||||
<tr data-tip="Define how many organized (!) religions and cults should be generated">
|
||||
<td>
|
||||
<i data-locked=0 id="lock_religions" class="icon-lock-open"></i>
|
||||
</td>
|
||||
|
|
@ -1625,16 +1626,28 @@
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
<p data-tip="Interface settings that don't affect generation. They are applied immediately on change">User Interface:</p>
|
||||
<p data-tip="Tool settings that don't affect maps. Changes are getting applied immediately">Generator settings:</p>
|
||||
<table>
|
||||
<tr data-tip="Set user interface size">
|
||||
<tr data-tip="Set what Generator should do on opening">
|
||||
<td></td>
|
||||
<td>Onload behavior</td>
|
||||
<td>
|
||||
<select id="onloadMap" data-stored="onloadMap">
|
||||
<option value="random" selected>Generate random map</option>
|
||||
<option value="saved">Open last saved map</option>
|
||||
</select>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Set user interface size. Please note browser zoom also affects interface size (Ctrl + or Ctrl - to change)">
|
||||
<td></td>
|
||||
<td>Interface size</td>
|
||||
<td>
|
||||
<input id="uiSizeInput" data-stored="uiSize" type="range" min=.6 max=1.4 step=.1 value=1>
|
||||
<input id="uiSizeInput" data-stored="uiSize" type="range" min=.8 max=2 step=.1 value=1>
|
||||
</td>
|
||||
<td>
|
||||
<input id="uiSizeOutput" data-stored="uiSize" type="number" min=.6 max=1.4 step=.1 value=1>
|
||||
<input id="uiSizeOutput" data-stored="uiSize" type="number" min=.6 max=2 step=.1 value=1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
|
@ -1801,7 +1814,13 @@
|
|||
<p>Join our <a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a> and <a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask questions, get help and share created maps. You may support the project on <a href='https://www.patreon.com/azgaar' target='_blank'>Patreon</a>.</p>
|
||||
<p>The project is under active development. For older versions see the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog" target="_blank">changelog</a>. To track the development progress see the <a href="https://trello.com/b/7x832DG4/fantasy-map-generator" target="_blank">devboard</a>. Please report bugs <a href="https://github.com/Azgaar/Fantasy-Map-Generator/issues" target="_blank">here</a>. You can also contact me directly via <a href="mailto:maxganiev@yandex.ru" target="_blank">email</a>.</p>
|
||||
<p>A special thanks to all supporters! <i data-tip="Click to see supporters names" class="collapsible icon-down-open pointer"></i></p>
|
||||
<p style="display:none">Supporters: Aaron Meyer, Ahmad Amerih, AstralJacks, aymeric, Billy Dean Goehring, Branndon Edwards, Chase Mayers, Curt Flood, cyninge, Dino Princip, E.M. White, es, Fondue, Fritjof Olsson, Gatsu, Johan Fröberg, Jonathan Moore, Joseph Miranda, Kate, KC138, Luke Nelson, Markus Finster, Massimo Vella, Mikey, Nathan Mitchell, Paavi1, Pat, Ryan Westcott, Sasquatch, Shawn Spencer, Sizz_TV, Timothée CALLET, UTG community, Vlad Tomash, Wil Sisney, William Merriott, Xariun, Gun Metal Games, Scott Marner, Spencer Sherman, Valerii Matskevych, Alloyed Clavicle, Stewart Walsh, Ruthlyn Mollett (Javan), Benjamin Mair-Pratt, Diagonath, Alexander Thomas, Ashley Wilson-Savoury, William Henry, Preston Brooks, JOSHUA QUALTIERI and many others!</p>
|
||||
<p style="display:none">Supporters: Aaron Meyer, Ahmad Amerih, AstralJacks, aymeric, Billy Dean Goehring, Branndon Edwards,
|
||||
Chase Mayers, Curt Flood, cyninge, Dino Princip, E.M. White, es, Fondue, Fritjof Olsson, Gatsu, Johan Fröberg, Jonathan Moore,
|
||||
Joseph Miranda, Kate, KC138, Luke Nelson, Markus Finster, Massimo Vella, Mikey, Nathan Mitchell, Paavi1, Pat, Ryan Westcott,
|
||||
Sasquatch, Shawn Spencer, Sizz_TV, Timothée CALLET, UTG community, Vlad Tomash, Wil Sisney, William Merriott, Xariun,
|
||||
Gun Metal Games, Scott Marner, Spencer Sherman, Valerii Matskevych, Alloyed Clavicle, Stewart Walsh, Ruthlyn Mollett (Javan),
|
||||
Benjamin Mair-Pratt, Diagonath, Alexander Thomas, Ashley Wilson-Savoury, William Henry, Preston Brooks, JOSHUA QUALTIERI,
|
||||
Hilton Williams, Katharina Haase, Hisham Bedri, Ian arless, Karnat, Bird, Kevin and many others!</p>
|
||||
|
||||
<ul class="share-buttons">
|
||||
<li><a href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator%2F"e=" data-tip="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="images/Facebook.png" /></a></li>
|
||||
|
|
@ -1813,14 +1832,22 @@
|
|||
</div>
|
||||
|
||||
<div id="sticked">
|
||||
<button id="newMapButton" data-tip="Generate a new map based on options. Shortcut: F7">New Map</button>
|
||||
<button id="saveButton" data-tip="Select file format to save map">Save as</button>
|
||||
<button id="newMapButton" data-tip="Generate a new map based on options. Shortcut: F2">New Map</button>
|
||||
<button id="saveButton" data-tip="Select file format to save map">Save</button>
|
||||
<div id="saveDropdown">
|
||||
<div id="saveMap" data-tip="Save as fully functional map in .map format. Shortcut: Ctrl + M">.map</div>
|
||||
<div id="saveSVG" data-tip="Download the map as .svg image (open in browser or Inkscape). Shortcut: Ctrl + S">.svg</div>
|
||||
<div id="savePNG" data-tip="Download visible part of the map as image. Texture will not be shown. Shortcut: Ctrl + P">.png</div>
|
||||
<div id="saveMap" data-tip="Download the map as fully functional .map file to your machine. Shortcut: Ctrl + M">.map</div>
|
||||
<div id="saveSVG" data-tip="Download the map as vector image (open in browser or Inkscape). Shortcut: Ctrl + S">.svg</div>
|
||||
<div id="savePNG" data-tip="Download visible part of the map as .png image. Shortcut: Ctrl + P">.png</div>
|
||||
<div id="quickSave" data-tip="Save map to browser storage. Shortcut: F6">storage</div>
|
||||
<!-- <div id="saveDropbox" data-tip="Save fully functional .map file to Dropbox. Shortcut: Ctrl + B">Dropbox</div> -->
|
||||
</div>
|
||||
<button id="loadButton" data-tip="Load fully functional map in a .map format">Load</button>
|
||||
<div id="loadDropdown">
|
||||
<div id="loadMap" data-tip="Load .map file from local disk. Shortcut: Ctrl + L">from local disk</div>
|
||||
<div id="loadURL" data-tip="Load .map file from URL (server should allow CORS). Shortcut: Ctrl + U">from URL</div>
|
||||
<div id="quickLoad" data-tip="Load map from browser storage (if saved before). Shortcut: F9">from storage</div>
|
||||
<!-- <div id="loadDropbox" data-tip="Load .map file from Dropbox. Shortcut: Ctrl + D">from Dropbox</div> -->
|
||||
</div>
|
||||
<button id="loadMap" data-tip="Load fully functional map in a .map format. Shortcut: Ctrl + L">Load</button>
|
||||
<button id="zoomReset" data-tip="Reset map zoom. Shortcut: 0">Reset Zoom</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -1994,7 +2021,7 @@
|
|||
<span id="riverReset" data-tip="Reset transformation to default" class="icon-cancel pointer"></span>
|
||||
</div>
|
||||
|
||||
<button id="riverLength" data-tip="Route length in selected units">0</button>
|
||||
<button id="riverLength" data-tip="River length in selected units">0</button>
|
||||
<button id="riverCopy" data-tip="Copy river" class="icon-clone"></button>
|
||||
<button id="riverNew" data-tip="Create new river clicking on map" class="icon-map-pin"></button>
|
||||
<button id="riverLegend" data-tip="Edit free text notes (legend) for the river" class="icon-edit"></button>
|
||||
|
|
@ -2010,7 +2037,7 @@
|
|||
<span id="routeGroupAdd" data-tip="Create new group for this route" class="icon-plus pointer"></span>
|
||||
<span id="routeGroupRemove" data-tip="Remove all routes of this group" class="icon-trash-empty pointer"></span>
|
||||
</div>
|
||||
<button id="routeLength" data-tip="River length in selected units">0</button>
|
||||
<button id="routeLength" data-tip="Route length in selected units">0</button>
|
||||
<button id="routeSplit" data-tip="Click on a control point to split the route" class="icon-unlink"></button>
|
||||
<button id="routeLegend" data-tip="Edit free text notes (legend) for the route" class="icon-edit"></button>
|
||||
<button id="routeNew" data-tip="Create new route clicking on map" class="icon-map-pin"></button>
|
||||
|
|
@ -2940,11 +2967,12 @@
|
|||
<script src="libs/polylabel.min.js"></script>
|
||||
<script src="libs/jquery-ui.min.js"></script>
|
||||
<script src="libs/seedrandom.min.js"></script>
|
||||
|
||||
<script src="modules/ui/layers.js"></script>
|
||||
|
||||
<script defer src="modules/ui/general.js"></script>
|
||||
<script defer src="modules/ui/options.js"></script>
|
||||
<script defer src="modules/ui/measurers.js"></script>
|
||||
<script defer src="modules/save-and-load.js"></script>
|
||||
<script defer src="main.js?version=1.0"></script>
|
||||
<script defer src="modules/relief-icons.js"></script>
|
||||
<script defer src="modules/ui/tools.js"></script>
|
||||
|
|
@ -2969,7 +2997,7 @@
|
|||
<script defer src="modules/ui/diplomacy-editor.js"></script>
|
||||
<script defer src="modules/ui/zones-editor.js"></script>
|
||||
<script defer src="modules/ui/editors.js"></script>
|
||||
<script defer src="modules/save-and-load.js"></script>
|
||||
<script defer src="libs/quantize.min.js"></script>
|
||||
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
||||
<!-- <script defer src="https://www.dropbox.com/static/api/2/dropins.js" id="dropboxjs" data-app-key="a0ls5yab5ly1ot5"></script> -->
|
||||
</body>
|
||||
|
|
|
|||
364
main.js
364
main.js
|
|
@ -10,6 +10,12 @@
|
|||
const version = "1.0"; // generator version
|
||||
document.title += " v" + version;
|
||||
|
||||
// if map version is not stored, clear localStorage and show a message
|
||||
if (localStorage.getItem("version") != version) {
|
||||
localStorage.clear();
|
||||
setTimeout(showWelcomeMessage, 8000);
|
||||
}
|
||||
|
||||
// append svg layers (in default order)
|
||||
let svg = d3.select("#map");
|
||||
let defs = svg.select("#deftemp");
|
||||
|
|
@ -81,9 +87,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 +116,169 @@ 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
|
||||
void function removeLoading() {
|
||||
d3.select("#loading").transition().duration(5000).style("opacity", 0).remove();
|
||||
d3.select("#initial").transition().duration(5000).attr("opacity", 0).remove();
|
||||
d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1);
|
||||
d3.select("#tooltip").transition().duration(3000).style("opacity", 1);
|
||||
}()
|
||||
|
||||
// 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 <b>${version}</b>.
|
||||
// decide which map should be loaded or generated on page load
|
||||
void function checkLoadParameters() {
|
||||
const url = new URL(window.location.href);
|
||||
const params = url.searchParams;
|
||||
|
||||
This version is compatible with versions 0.8b and 0.9b, but not with older .map files.
|
||||
Please use an <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog' target='_blank'>archived version</a> to open old files.
|
||||
|
||||
<ul><a href=${link} target='_blank'>Main changes:</a>
|
||||
<li>Provinces and Provinces Editor</li>
|
||||
<li>Religions Layer and Religions Editor</li>
|
||||
<li>Full state names (state types)</li>
|
||||
<li>Multi-lined labels</li>
|
||||
<li>State relations (diplomacy)</li>
|
||||
<li>Custom layers (zones)</li>
|
||||
<li>Places of interest (auto-added markers)</li>
|
||||
<li>New color picker and hatching fill</li>
|
||||
<li>Legend boxes</li>
|
||||
<li>World Configurator presets</li>
|
||||
<li>Improved state labels placement</li>
|
||||
<li>Relief icons sets</li>
|
||||
<li>Fogging</li>
|
||||
<li>Custom layer presets</li>
|
||||
<li>Custom biomes</li>
|
||||
<li>State, province and burg COAs</li>
|
||||
<li>Desktop version (see <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A#is-there-a-desktop-version' target='_blank'>here)</a></li>
|
||||
</ul>
|
||||
|
||||
<p>Join our <a href='https://www.reddit.com/r/FantasyMapGenerator' target='_blank'>Reddit community</a> and
|
||||
<a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a>
|
||||
to ask questions, share maps, discuss the Generator, report bugs and propose new features.</p>
|
||||
|
||||
<p>Thanks for all supporters on <a href='https://www.patreon.com/azgaar' target='_blank'>Patreon</a>!</i></p>`;
|
||||
|
||||
$("#alert").dialog(
|
||||
{resizable: false, title: "Fantasy Map Generator update", width: 310,
|
||||
buttons: {
|
||||
OK: function() {
|
||||
localStorage.clear();
|
||||
localStorage.setItem("version", version);
|
||||
$(this).dialog("close");
|
||||
// of there is a valid maplink, try to load .map file from URL
|
||||
if (params.get("maplink")) {
|
||||
console.warn("Load map from URL");
|
||||
const maplink = params.get("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 not a valid URL", maplink);
|
||||
}
|
||||
},
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
|
||||
// if there is a seed (user of MFCG provided), generate map for it
|
||||
if (params.get("seed")) {
|
||||
console.warn("Generate map for seed");
|
||||
generateMapOnLoad();
|
||||
return;
|
||||
}
|
||||
|
||||
// open latest map if option is active and map is stored
|
||||
if (onloadMap.value === "saved") {
|
||||
ldb.get("lastMap", blob => {
|
||||
if (blob) {
|
||||
console.warn("Load last saved map");
|
||||
try {
|
||||
uploadFile(blob);
|
||||
}
|
||||
catch(error) {
|
||||
console.error(error);
|
||||
console.warn("Cannot load stored map, random map to be generated");
|
||||
generateMapOnLoad();
|
||||
}
|
||||
} else {
|
||||
console.error("No map stored, random map to be generated");
|
||||
generateMapOnLoad();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn("Generate random map");
|
||||
generateMapOnLoad();
|
||||
}()
|
||||
|
||||
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) generateMapOnLoad();
|
||||
});
|
||||
}
|
||||
|
||||
function showUploadErrorMessage(error, URL, random) {
|
||||
console.error(error);
|
||||
alertMessage.innerHTML = `
|
||||
Cannot load map from the <a href='${URL}' target='_blank'>link provided</a>.
|
||||
${random?`A new random map is generated. `:''}
|
||||
Please ensure the linked file is reachable and CORS is allowed on server side`;
|
||||
$("#alert").dialog({title: "Loading error", width: 320, buttons: {OK: function() {$(this).dialog("close");}}});
|
||||
}
|
||||
|
||||
function generateMapOnLoad() {
|
||||
applyDefaultStyle(); // apply style
|
||||
generate(); // generate map
|
||||
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
|
||||
applyPreset(); // apply saved layers preset
|
||||
}
|
||||
|
||||
// focus on coordinates, cell or burg provided in searchParams
|
||||
function focusOn() {
|
||||
const url = new URL(window.location.href);
|
||||
const params = url.searchParams;
|
||||
|
||||
if (params.get("from") === "MFCG") {
|
||||
if (params.get("seed").length === 13) {
|
||||
// show back burg from MFCG
|
||||
params.set("burg", params.get("seed").slice(-4));
|
||||
} else {
|
||||
// select burg for MFCG
|
||||
findBurgForMFCG(params);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const s = +params.get("scale") || 8;
|
||||
let x = +params.get("x");
|
||||
let y = +params.get("y");
|
||||
|
||||
const c = +params.get("cell");
|
||||
if (c) {
|
||||
x = pack.cells.p[c][0];
|
||||
y = pack.cells.p[c][1];
|
||||
}
|
||||
|
||||
const b = +params.get("burg");
|
||||
if (b && pack.burgs[b]) {
|
||||
x = pack.burgs[b].x;
|
||||
y = pack.burgs[b].y;
|
||||
}
|
||||
|
||||
if (x && y) zoomTo(x, y, s, 1600);
|
||||
}
|
||||
|
||||
// find burg for MFCG and focus on it
|
||||
function findBurgForMFCG(params) {
|
||||
const cells = pack.cells, burgs = pack.burgs;
|
||||
if (pack.burgs.length < 2) {console.error("Cannot select a burg for MFCG"); return;}
|
||||
|
||||
const size = +params.get("size");
|
||||
const name = params.get("name");
|
||||
let coast = +params.get("coast");
|
||||
let port = +params.get("port");
|
||||
let river = +params.get("river");
|
||||
|
||||
let selection = defineSelection(coast, port, river);
|
||||
if (!selection.length) selection = defineSelection(coast, !port, !river);
|
||||
if (!selection.length) selection = defineSelection(!coast, 0, !river);
|
||||
if (!selection.length) selection = [burgs[1]]; // select first if nothing is found
|
||||
|
||||
function defineSelection(coast, port, river) {
|
||||
if (port && river) return burgs.filter(b => b.port && cells.r[b.cell]);
|
||||
if (!port && coast && river) return burgs.filter(b => !b.port && cells.t[b.cell] === 1 && cells.r[b.cell]);
|
||||
if (!coast && !river) return burgs.filter(b => cells.t[b.cell] !== 1 && !cells.r[b.cell]);
|
||||
if (!coast && river) return burgs.filter(b => cells.t[b.cell] !== 1 && cells.r[b.cell]);
|
||||
if (coast && river) return burgs.filter(b => cells.t[b.cell] === 1 && cells.r[b.cell]);
|
||||
return [];
|
||||
}
|
||||
|
||||
// select a burg with closest population from selection
|
||||
const selected = d3.scan(selection, (a, b) => Math.abs(a.population - size) - Math.abs(b.population - size));
|
||||
const b = selection[selected].i;
|
||||
if (!b) {console.error("Cannot select a burg for MFCG"); return;}
|
||||
if (size) burgs[b].population = size;
|
||||
if (name) burgs[b].name = name;
|
||||
|
||||
const label = burgLabels.select("[data-id='" + b + "']");
|
||||
if (label.size()) {
|
||||
tip("Here stands the glorious city of " + burgs[b].name, true, "success", 12000);
|
||||
label.text(burgs[b].name).classed("drag", true).on("mouseover", function() {
|
||||
d3.select(this).classed("drag", false);
|
||||
label.on("mouseover", null);
|
||||
});
|
||||
}
|
||||
|
||||
zoomTo(burgs[b].x, burgs[b].y, 8, 1600);
|
||||
invokeActiveZooming();
|
||||
}
|
||||
|
||||
function applyDefaultNamesData() {
|
||||
|
|
@ -364,83 +477,45 @@ function applyDefaultStyle() {
|
|||
fogging.attr("opacity", .8).attr("fill", "#000000").attr("stroke-width", 5);
|
||||
}
|
||||
|
||||
// focus on coordinates, cell or burg provided in searchParams
|
||||
function focusOn() {
|
||||
const url = new URL(window.location.href);
|
||||
const params = url.searchParams;
|
||||
function showWelcomeMessage() {
|
||||
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 <b>${version}</b>.
|
||||
|
||||
if (params.get("from") === "MFCG") {
|
||||
if (params.get("seed").length === 13) {
|
||||
// show back burg from MFCG
|
||||
params.set("burg", params.get("seed").slice(-4));
|
||||
} else {
|
||||
// select burg for MFCG
|
||||
findBurgForMFCG(params);
|
||||
return;
|
||||
}
|
||||
}
|
||||
This version is compatible with versions 0.8b and 0.9b, but not with older .map files.
|
||||
Please use an <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog' target='_blank'>archived version</a> to open old files.
|
||||
|
||||
const s = +params.get("scale") || 8;
|
||||
let x = +params.get("x");
|
||||
let y = +params.get("y");
|
||||
<ul><a href=${link} target='_blank'>Main changes:</a>
|
||||
<li>Provinces and Provinces Editor</li>
|
||||
<li>Religions Layer and Religions Editor</li>
|
||||
<li>Full state names (state types)</li>
|
||||
<li>Multi-lined labels</li>
|
||||
<li>State relations (diplomacy)</li>
|
||||
<li>Custom layers (zones)</li>
|
||||
<li>Places of interest (auto-added markers)</li>
|
||||
<li>New color picker and hatching fill</li>
|
||||
<li>Legend boxes</li>
|
||||
<li>World Configurator presets</li>
|
||||
<li>Improved state labels placement</li>
|
||||
<li>Relief icons sets</li>
|
||||
<li>Fogging</li>
|
||||
<li>Custom layer presets</li>
|
||||
<li>Custom biomes</li>
|
||||
<li>State, province and burg COAs</li>
|
||||
<li>Desktop version (see <a href='https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A#is-there-a-desktop-version' target='_blank'>here)</a></li>
|
||||
</ul>
|
||||
|
||||
const c = +params.get("cell");
|
||||
if (c) {
|
||||
x = pack.cells.p[c][0];
|
||||
y = pack.cells.p[c][1];
|
||||
}
|
||||
<p>Join our <a href='https://www.reddit.com/r/FantasyMapGenerator' target='_blank'>Reddit community</a> and
|
||||
<a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a>
|
||||
to ask questions, share maps, discuss the Generator, report bugs and propose new features.</p>
|
||||
|
||||
const b = +params.get("burg");
|
||||
if (b && pack.burgs[b]) {
|
||||
x = pack.burgs[b].x;
|
||||
y = pack.burgs[b].y;
|
||||
}
|
||||
<p>Thanks for all supporters on <a href='https://www.patreon.com/azgaar' target='_blank'>Patreon</a>!</i></p>`;
|
||||
|
||||
if (x && y) zoomTo(x, y, s, 1600);
|
||||
}
|
||||
|
||||
// find burg for MFCG and focus on it
|
||||
function findBurgForMFCG(params) {
|
||||
const cells = pack.cells, burgs = pack.burgs;
|
||||
if (pack.burgs.length < 2) {console.error("Cannot select a burg for MFCG"); return;}
|
||||
|
||||
const size = +params.get("size");
|
||||
let coast = +params.get("coast");
|
||||
let port = +params.get("port");
|
||||
let river = +params.get("river");
|
||||
|
||||
let selection = defineSelection(coast, port, river);
|
||||
if (!selection.length) selection = defineSelection(coast, !port, !river);
|
||||
if (!selection.length) selection = defineSelection(!coast, 0, !river);
|
||||
if (!selection.length) selection = [burgs[1]]; // select first if nothing is found
|
||||
|
||||
function defineSelection(coast, port, river) {
|
||||
if (port && river) return burgs.filter(b => b.port && cells.r[b.cell]);
|
||||
if (!port && coast && river) return burgs.filter(b => !b.port && cells.t[b.cell] === 1 && cells.r[b.cell]);
|
||||
if (!coast && !river) return burgs.filter(b => cells.t[b.cell] !== 1 && !cells.r[b.cell]);
|
||||
if (!coast && river) return burgs.filter(b => cells.t[b.cell] !== 1 && cells.r[b.cell]);
|
||||
if (coast && river) return burgs.filter(b => cells.t[b.cell] === 1 && cells.r[b.cell]);
|
||||
return [];
|
||||
}
|
||||
|
||||
// select a burg with closest population from selection
|
||||
const selected = d3.scan(selection, (a, b) => Math.abs(a.population - size) - Math.abs(b.population - size));
|
||||
const b = selection[selected].i;
|
||||
if (!b) {console.error("Cannot select a burg for MFCG"); return;}
|
||||
if (size) burgs[b].population = size;
|
||||
|
||||
const label = burgLabels.select("[data-id='" + b + "']");
|
||||
if (label.size()) {
|
||||
tip("Here stands the glorious city of " + burgs[b].name, true, "error");
|
||||
label.classed("drag", true).on("mouseover", function() {
|
||||
d3.select(this).classed("drag", false);
|
||||
label.on("mouseover", null);
|
||||
tip("", true);
|
||||
});
|
||||
}
|
||||
|
||||
zoomTo(burgs[b].x, burgs[b].y, 8, 1600);
|
||||
invokeActiveZooming();
|
||||
$("#alert").dialog(
|
||||
{resizable: false, title: "Fantasy Map Generator update", width: 310,
|
||||
buttons: {OK: function() {$(this).dialog("close")}},
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
close: () => localStorage.setItem("version", version)}
|
||||
);
|
||||
}
|
||||
|
||||
function zoomed() {
|
||||
|
|
@ -561,6 +636,7 @@ void function addDragToUpload() {
|
|||
}
|
||||
// all good - show uploading text and load the map
|
||||
$("#map-dragged > p").text("Uploading<span>.</span><span>.</span><span>.</span>");
|
||||
closeDialogs();
|
||||
uploadFile(file, function onUploadFinish() {
|
||||
$("#map-dragged > p").text("Drop to upload");
|
||||
});
|
||||
|
|
@ -568,11 +644,11 @@ void function addDragToUpload() {
|
|||
}()
|
||||
|
||||
function generate() {
|
||||
try {
|
||||
const timeStart = performance.now();
|
||||
console.time("TOTAL");
|
||||
invokeActiveZooming();
|
||||
generateSeed();
|
||||
console.group("Map " + seed);
|
||||
console.group("Generated Map " + seed);
|
||||
applyMapSize();
|
||||
randomizeOptions();
|
||||
placePoints();
|
||||
|
|
@ -606,7 +682,27 @@ function generate() {
|
|||
|
||||
console.warn(`TOTAL: ${rn((performance.now()-timeStart)/1000,2)}s`);
|
||||
showStatistics();
|
||||
console.groupEnd("Map " + seed);
|
||||
console.groupEnd("Generated Map " + seed);
|
||||
}
|
||||
catch(error) {
|
||||
console.error(error);
|
||||
clearMainTip();
|
||||
|
||||
const regex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
||||
const errorNoURL = error.stack.replace(regex, url => '<i>' + last(url.split("/")) + '</i>');
|
||||
const errorParsed = errorNoURL.replace(/at /ig, "<br> at ");
|
||||
|
||||
alertMessage.innerHTML = `An error is occured on map generation. Please retry.
|
||||
<br>If error persists, clear the stored data and try again.
|
||||
<p id="errorBox">${errorParsed}</p>`;
|
||||
$("#alert").dialog({
|
||||
resizable: false, title: "Generation error", maxWidth:500, buttons: {
|
||||
"Clear data": function() {localStorage.clear(); localStorage.setItem("version", version);},
|
||||
Regenerate: function() {regenerateMap(); $(this).dialog("close");}
|
||||
}, position: {my: "center", at: "center", of: "svg"}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// generate map seed (string!) or get it from URL searchParams
|
||||
|
|
@ -964,7 +1060,10 @@ function drawCoastline() {
|
|||
if (start === -1) continue; // cannot start here
|
||||
const connectedVertices = connectVertices(start, type);
|
||||
used[f] = 1;
|
||||
const points = connectedVertices.map(v => vertices.p[v]);
|
||||
let points = connectedVertices.map(v => vertices.p[v]);
|
||||
const area = d3.polygonArea(points); // area with lakes/islands
|
||||
if (area > 0 && features[f].type === "lake") points = points.reverse();
|
||||
features[f].area = Math.abs(area);
|
||||
|
||||
const path = round(lineGen(points));
|
||||
const id = features[f].group + features[f].i;
|
||||
|
|
@ -1006,9 +1105,9 @@ function drawCoastline() {
|
|||
const c0 = c[0] >= n || cells.t[c[0]] === t;
|
||||
const c1 = c[1] >= n || cells.t[c[1]] === t;
|
||||
const c2 = c[2] >= n || cells.t[c[2]] === t;
|
||||
if (v[0] !== prev && c0 !== c1) current = v[0];
|
||||
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
||||
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||
if (v[0] !== prev && c0 !== c1) current = v[0]; else
|
||||
if (v[1] !== prev && c1 !== c2) current = v[1]; else
|
||||
if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||
if (current === chain[chain.length-1]) {console.error("Next vertex is not found"); break;}
|
||||
}
|
||||
chain.push(chain[0]); // push first vertex as the last one
|
||||
|
|
@ -1389,6 +1488,7 @@ function showStatistics() {
|
|||
}
|
||||
|
||||
const regenerateMap = debounce(function() {
|
||||
console.warn("Generate new random map");
|
||||
closeDialogs("#worldConfigurator");
|
||||
customization = 0;
|
||||
undraw();
|
||||
|
|
|
|||
|
|
@ -103,21 +103,22 @@
|
|||
// place secondary settlements based on geo and economical evaluation
|
||||
function placeTowns() {
|
||||
console.time('placeTowns');
|
||||
const score = new Int16Array(cells.s.map(s => s * gauss(1,3,0,20,3))); // cell score for towns placement
|
||||
const sorted = cells.i.filter(i => score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
|
||||
const score = new Int16Array(cells.s.map(s => s * gauss(1,3,0,20,3))); // a bit randomized cell score for towns placement
|
||||
const sorted = cells.i.filter(i => !cells.burg[i] && score[i] > 0 && cells.culture[i]).sort((a, b) => score[b] - score[a]); // filtered and sorted array of indexes
|
||||
|
||||
const desiredNumber = manorsInput.value == 1000 ? rn(sorted.length / 8 / densityInput.value ** .8) : manorsInput.valueAsNumber;
|
||||
const burgsNumber = Math.min(desiredNumber, sorted.length);
|
||||
const burgsNumber = Math.min(desiredNumber, sorted.length); // towns to generate
|
||||
let burgsAdded = 0;
|
||||
|
||||
const burgsTree = burgs[0];
|
||||
let spacing = (graphWidth + graphHeight) / 150 / (burgsNumber ** .7 / 66); // min distance between towns
|
||||
|
||||
while (burgsAdded < burgsNumber) {
|
||||
while (burgsAdded < burgsNumber && spacing > 1) {
|
||||
for (let i=0; burgsAdded < burgsNumber && i < sorted.length; i++) {
|
||||
if (cells.burg[sorted[i]]) continue;
|
||||
const cell = sorted[i], x = cells.p[cell][0], y = cells.p[cell][1];
|
||||
const s = spacing * gauss(1, .3, .2, 2, 2); // randomize to make the placement not uniform
|
||||
if (cells.burg[cell] || burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg
|
||||
const s = spacing * gauss(1, .3, .2, 2, 2); // randomize to make placement not uniform
|
||||
if (burgsTree.find(x, y, s) !== undefined) continue; // to close to existing burg
|
||||
const burg = burgs.length;
|
||||
const culture = cells.culture[cell];
|
||||
const name = Names.getCulture(culture);
|
||||
|
|
@ -133,11 +134,7 @@
|
|||
console.error(`Cannot place all burgs. Requested ${desiredNumber}, placed ${burgsAdded}`);
|
||||
}
|
||||
|
||||
//const min = d3.min(score.filter(s => s)), max = d3.max(score);
|
||||
//terrs.selectAll("polygon").data(sorted).enter().append("polygon").attr("points", d => getPackPolygon(d)).attr("fill", d => color(1 - normalize(score[d], min, max)));
|
||||
//labels.selectAll("text").data(sorted).enter().append("text").attr("x", d => cells.p[d][0]).attr("y", d => cells.p[d][1]).text(d => score[d]).attr("font-size", 2);
|
||||
|
||||
burgs[0] = {name:undefined};
|
||||
burgs[0] = {name:undefined}; // do not store burgsTree anymore
|
||||
console.timeEnd('placeTowns');
|
||||
}
|
||||
}
|
||||
|
|
@ -265,6 +262,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 +277,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);
|
||||
|
|
@ -822,7 +820,7 @@
|
|||
cells.province = new Uint16Array(cells.i.length); // cell state
|
||||
const percentage = +provincesInput.value;
|
||||
if (states.length < 2 || !percentage) return; // no provinces
|
||||
const max = gauss(400, 50, 300, 500) / percentage ** .5; // max growth in 300-30 range
|
||||
const max = percentage == 100 ? 1000 : gauss(20, 5, 5, 100) * percentage ** .5; // max growth
|
||||
|
||||
const forms = {
|
||||
Monarchy:{County:11, Earldom:3, Shire:1, Landgrave:1, Margrave:1, Barony:1},
|
||||
|
|
|
|||
|
|
@ -151,11 +151,12 @@ function GFontToDataURI(url) {
|
|||
});
|
||||
}
|
||||
|
||||
// Save in .map format
|
||||
function saveMap() {
|
||||
if (customization) {tip("Map cannot be saved when is in edit mode, please exit the mode and retry", false, "error"); return;}
|
||||
console.time("saveMap");
|
||||
closeDialogs();
|
||||
// prepare map data for saving
|
||||
function getMapData() {
|
||||
if (customization) return false;
|
||||
console.time("createMapDataBlob");
|
||||
|
||||
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";
|
||||
|
|
@ -173,6 +174,10 @@ function saveMap() {
|
|||
viewbox.attr("transform", null);
|
||||
const svg_xml = (new XMLSerializer()).serializeToString(svg.node());
|
||||
|
||||
// restore initial values
|
||||
svg.attr("width", svgWidth).attr("height", svgHeight);
|
||||
zoom.transform(svg, transform);
|
||||
|
||||
const gridGeneral = JSON.stringify({spacing:grid.spacing, cellsX:grid.cellsX, cellsY:grid.cellsY, boundary:grid.boundary, points:grid.points, features:grid.features});
|
||||
const features = JSON.stringify(pack.features);
|
||||
const cultures = JSON.stringify(pack.cultures);
|
||||
|
|
@ -188,28 +193,33 @@ function saveMap() {
|
|||
pack.cells.biome, pack.cells.burg, pack.cells.conf, pack.cells.culture, pack.cells.fl,
|
||||
pack.cells.pop, pack.cells.r, pack.cells.road, pack.cells.s, pack.cells.state,
|
||||
pack.cells.religion, pack.cells.province, pack.cells.crossroad, religions, provinces].join("\r\n");
|
||||
const dataBlob = new Blob([data], {type: "text/plain"});
|
||||
const dataURL = window.URL.createObjectURL(dataBlob);
|
||||
const blob = new Blob([data], {type: "text/plain"});
|
||||
|
||||
console.timeEnd("createMapDataBlob");
|
||||
resolve(blob);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Download .map file
|
||||
async function saveMap() {
|
||||
if (customization) {tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return;}
|
||||
closeDialogs();
|
||||
|
||||
const blob = await getMapData();
|
||||
const URL = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.download = "fantasy_map_" + Date.now() + ".map";
|
||||
link.href = dataURL;
|
||||
link.href = URL;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + J) to check`, true, "warning");
|
||||
|
||||
// restore initial values
|
||||
svg.attr("width", svgWidth).attr("height", svgHeight);
|
||||
zoom.transform(svg, transform);
|
||||
|
||||
window.setTimeout(function() {
|
||||
window.URL.revokeObjectURL(dataURL);
|
||||
clearMainTip();
|
||||
}, 3000);
|
||||
console.timeEnd("saveMap");
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(URL), 5000);
|
||||
}
|
||||
|
||||
function uploadFile(file, callback) {
|
||||
console.time("loadMap");
|
||||
uploadFile.timeStart = performance.now();
|
||||
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = function(fileLoadedEvent) {
|
||||
const dataLoaded = fileLoadedEvent.target.result;
|
||||
|
|
@ -243,7 +253,7 @@ function uploadFile(file, callback) {
|
|||
}
|
||||
|
||||
function parseLoadedData(data) {
|
||||
closeDialogs();
|
||||
try {
|
||||
const reliefIcons = document.getElementById("defs-relief").innerHTML; // save relief icons
|
||||
const hatching = document.getElementById("hatching").cloneNode(true); // save hatching
|
||||
|
||||
|
|
@ -254,6 +264,8 @@ function parseLoadedData(data) {
|
|||
if (params[5]) graphHeight = +params[5];
|
||||
}()
|
||||
|
||||
console.group("Loaded Map " + seed);
|
||||
|
||||
void function parseOptions() {
|
||||
const options = data[1].split("|");
|
||||
if (options[0]) applyOption(distanceUnitInput, options[0]);
|
||||
|
|
@ -423,7 +435,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 +443,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() {
|
||||
|
|
@ -516,8 +531,108 @@ function parseLoadedData(data) {
|
|||
}()
|
||||
|
||||
changeMapSize();
|
||||
restoreDefaultEvents();
|
||||
if (window.restoreDefaultEvents) restoreDefaultEvents();
|
||||
invokeActiveZooming();
|
||||
tip("Map is loaded");
|
||||
console.timeEnd("loadMap");
|
||||
|
||||
console.warn(`TOTAL: ${rn((performance.now()-uploadFile.timeStart)/1000,2)}s`);
|
||||
showStatistics();
|
||||
console.groupEnd("Loaded Map " + seed);
|
||||
tip("Map is successfully loaded", true, "success", 7000);
|
||||
}
|
||||
catch(error) {
|
||||
console.error(error);
|
||||
clearMainTip();
|
||||
|
||||
const regex =/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
|
||||
const errorNoURL = error.stack.replace(regex, url => '<i>' + last(url.split("/")) + '</i>');
|
||||
const errorParsed = errorNoURL.replace(/ at /ig, "<br> at ");
|
||||
|
||||
alertMessage.innerHTML = `An error is occured on map loading. Select a different file to load,
|
||||
<br>generate a new random map or cancel the loading
|
||||
<p id="errorBox">${errorParsed}</p>`;
|
||||
$("#alert").dialog({
|
||||
resizable: false, title: "Loading error", maxWidth:500, buttons: {
|
||||
"Select file": function() {$(this).dialog("close"); mapToLoad.click();},
|
||||
"New map": function() {$(this).dialog("close"); regenerateMap();},
|
||||
Cancel: function() {$(this).dialog("close")}
|
||||
}, position: {my: "center", at: "center", of: "svg"}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function quickSave() {
|
||||
const blob = await getMapData();
|
||||
if (blob) ldb.set("lastMap", blob); // auto-save map
|
||||
tip("Map is saved to browser memory", true, "success", 2000);
|
||||
}
|
||||
|
||||
function quickLoad() {
|
||||
ldb.get("lastMap", blob => {
|
||||
if (blob) {
|
||||
loadMapPrompt(blob);
|
||||
} else {
|
||||
tip("No map stored. Save map to storage first", true, "error", 2000);
|
||||
console.error("No map stored");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadMapPrompt(blob) {
|
||||
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
||||
if (workingTime < 10) {loadLastSavedMap(); return;}
|
||||
|
||||
alertMessage.innerHTML = `Are you sure you want to load saved map?<br>
|
||||
All unsaved changes made to the current map will be lost`;
|
||||
$("#alert").dialog({resizable: false, title: "Load saved map",
|
||||
buttons: {
|
||||
Cancel: function() {$(this).dialog("close");},
|
||||
Load: function() {loadLastSavedMap(); $(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
|
||||
function loadLastSavedMap() {
|
||||
console.warn("Load last saved map");
|
||||
closeDialogs();
|
||||
try {
|
||||
uploadFile(blob);
|
||||
}
|
||||
catch(error) {
|
||||
console.error(error);
|
||||
tip("Cannot load last saved map", true, "error", 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 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);
|
||||
}, 1e6);
|
||||
saveReminder.status = 1;
|
||||
}
|
||||
|
||||
saveReminder();
|
||||
|
||||
function toggleSaveReminder() {
|
||||
if (saveReminder.status) {
|
||||
tip("Save reminder is turned off. Press CTRL+Q again to re-initiate", true, "warn", 2000);
|
||||
clearInterval(saveReminder.reminder);
|
||||
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");
|
||||
saveReminder();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ function editBurgs() {
|
|||
|
||||
function getCultureOptions(culture) {
|
||||
let options = "";
|
||||
pack.cultures.slice(1).forEach(c => options += `<option ${c.i === culture ? "selected" : ""} value="${c.i}">${c.name}</option>`);
|
||||
pack.cultures.forEach(c => options += `<option ${c.i === culture ? "selected" : ""} value="${c.i}">${c.name}</option>`);
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ function editBurgs() {
|
|||
function changeBurgPopulation() {
|
||||
const burg = +this.parentNode.dataset.id;
|
||||
if (this.value == "" || isNaN(+this.value)) {
|
||||
tip("Please provide an integer number", false, "error");
|
||||
tip("Please provide an integer number (like 10000, not 10K)", false, "error");
|
||||
this.value = si(pack.burgs[burg].population * populationRate.value * urbanization.value);
|
||||
return;
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ function editCultures() {
|
|||
cults.select("#culture"+culture).remove();
|
||||
debug.select("#cultureCenter"+culture).remove();
|
||||
|
||||
pack.burgs.filter(b => b.culture === culture).forEach(b => b.culture = 0);
|
||||
pack.burgs.filter(b => b.culture == culture).forEach(b => b.culture = 0);
|
||||
pack.cells.culture.forEach((c, i) => {if(c === culture) pack.cells.culture[i] = 0;});
|
||||
pack.cultures[culture].removed = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -187,22 +187,43 @@ function editDiplomacy() {
|
|||
const chronicle = pack.states[0].diplomacy;
|
||||
if (!chronicle.length) {tip("Relations history is blank", false, "error"); return;}
|
||||
|
||||
let message = `<div>`;
|
||||
chronicle.forEach(e => {
|
||||
message += `<div style="margin: 0.5em 0">`;
|
||||
e.forEach((l, i) => message += `<div${i ? "" : " style='font-weight:bold'"}>${l}</div>`);
|
||||
message += `</div>`;
|
||||
let message = `<div autocorrect="off" spellcheck="false">`;
|
||||
chronicle.forEach((e, d) => {
|
||||
message += `<div>`;
|
||||
e.forEach((l, i) => message += `<div contenteditable="true" data-id="${d}-${i}"${i ? "" : " style='font-weight:bold'"}>${l}</div>`);
|
||||
message += `‍</div>`;
|
||||
});
|
||||
alertMessage.innerHTML = message + `</div>`;
|
||||
alertMessage.innerHTML = message + `</div><i id="info-line">Type to edit. Press Enter to add a new line, empty the element to remove it</i>`;
|
||||
alertMessage.querySelectorAll("div[contenteditable='true']").forEach(el => el.addEventListener("input", changeReliationsHistory));
|
||||
|
||||
$("#alert").dialog({title: "Relations history", position: {my: "center", at: "center", of: "svg"},
|
||||
buttons: {
|
||||
Save: function() {
|
||||
const text = this.querySelector("div").innerText.split("\n").join("\r\n");
|
||||
const dataBlob = new Blob([text], {type: "text/plain"});
|
||||
const url = window.URL.createObjectURL(dataBlob);
|
||||
const link = document.createElement("a");
|
||||
document.body.appendChild(link);
|
||||
link.download = "state_relations_history" + Date.now() + ".txt";
|
||||
link.href = url;
|
||||
link.click();
|
||||
window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000);
|
||||
},
|
||||
Clear: function() {pack.states[0].diplomacy = []; $(this).dialog("close");},
|
||||
Close: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function changeReliationsHistory() {
|
||||
const i = this.dataset.id.split("-");
|
||||
const group = pack.states[0].diplomacy[i[0]];
|
||||
if (this.innerHTML === "") {
|
||||
group.splice(i[1], 1);
|
||||
this.remove();
|
||||
} else group[i[1]] = this.innerHTML;
|
||||
}
|
||||
|
||||
function showRelationsMatrix() {
|
||||
const states = pack.states.filter(s => s.i && !s.removed);
|
||||
const valid = states.map(s => s.i);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// Module to store general UI functions
|
||||
"use strict";
|
||||
|
||||
// ask before closing the window
|
||||
window.onbeforeunload = () => "Are you sure you want to navigate away?";
|
||||
|
||||
// fit full-screen map if window is resized
|
||||
$(window).resize(function(e) {
|
||||
mapWidthInput.value = window.innerWidth;
|
||||
|
|
@ -11,6 +8,8 @@ $(window).resize(function(e) {
|
|||
changeMapSize();
|
||||
});
|
||||
|
||||
window.onbeforeunload = () => "Are you sure you want to navigate away?";
|
||||
|
||||
// Tooltips
|
||||
const tooltip = document.getElementById("tooltip");
|
||||
|
||||
|
|
@ -18,16 +17,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;
|
||||
}
|
||||
|
||||
|
|
@ -229,14 +230,21 @@ function applyOption(select, option) {
|
|||
document.addEventListener("keydown", function(event) {
|
||||
const active = document.activeElement.tagName;
|
||||
if (active === "INPUT" || active === "SELECT" || active === "TEXTAREA") return; // don't trigger if user inputs a text
|
||||
if (active === "DIV" && document.activeElement.contentEditable === "true") return; // don't trigger if user inputs a text
|
||||
const key = event.keyCode, ctrl = event.ctrlKey, shift = event.shiftKey;
|
||||
if (key === 118) regeneratePrompt(); // "F7" for new map
|
||||
else if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs
|
||||
if (key === 27) {closeDialogs(); hideOptions();} // Escape to close all dialogs
|
||||
else if (key === 9) {toggleOptions(event); event.preventDefault();} // Tab to toggle options
|
||||
|
||||
else if (key === 113) regeneratePrompt(); // "F2" for new map
|
||||
else if (key === 117) quickSave(); // "F6" for quick save
|
||||
else if (key === 120) quickLoad(); // "F9" for quick load
|
||||
|
||||
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 (ctrl && key === 81) toggleSaveReminder(); // Ctrl + "Q" to toggle save reminder
|
||||
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
|
||||
|
|
|
|||
|
|
@ -22,8 +22,12 @@ function restoreLayers() {
|
|||
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
|
||||
}
|
||||
|
||||
// layers to be turned on; changable by user
|
||||
let presets = {
|
||||
restoreLayers(); // run on-load
|
||||
let presets = {}; // global object
|
||||
restoreCustomPresets(); // run on-load
|
||||
|
||||
function getDefaultPresets() {
|
||||
return {
|
||||
"political": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar", "toggleStates"],
|
||||
"cultural": ["toggleBorders", "toggleCultures", "toggleIcons", "toggleLabels", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
||||
"religions": ["toggleBorders", "toggleIcons", "toggleLabels", "toggleReligions", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
||||
|
|
@ -33,11 +37,10 @@ let presets = {
|
|||
"poi": ["toggleBorders", "toggleHeight", "toggleIcons", "toggleMarkers", "toggleRivers", "toggleRoutes", "toggleScaleBar"],
|
||||
"landmass": ["toggleScaleBar"]
|
||||
}
|
||||
|
||||
restoreLayers(); // run on-load
|
||||
restoreCustomPresets(); // run on-load
|
||||
}
|
||||
|
||||
function restoreCustomPresets() {
|
||||
presets = getDefaultPresets();
|
||||
const storedPresets = JSON.parse(localStorage.getItem("presets"));
|
||||
if (!storedPresets) return;
|
||||
|
||||
|
|
@ -45,6 +48,7 @@ function restoreCustomPresets() {
|
|||
if (presets[preset]) continue;
|
||||
layersPreset.add(new Option(preset, preset));
|
||||
}
|
||||
|
||||
presets = storedPresets;
|
||||
}
|
||||
|
||||
|
|
@ -62,34 +66,50 @@ function changePreset(preset) {
|
|||
});
|
||||
layersPreset.value = preset;
|
||||
localStorage.setItem("preset", preset);
|
||||
|
||||
const isDefault = getDefaultPresets()[preset];
|
||||
removePresetButton.style.display = isDefault ? "none" : "inline-block";
|
||||
savePresetButton.style.display = "none";
|
||||
}
|
||||
|
||||
function savePreset() {
|
||||
// don't allow if layers should already esist as a preset
|
||||
if (layersPreset.value !== "custom") {
|
||||
tip(`Current layers are already saved as a "${layersPreset.selectedOptions[0].label}" preset`, false, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// add new preset
|
||||
const preset = prompt("Please provide a preset name"); // preset name
|
||||
if (!preset) return;
|
||||
presets[preset] = Array.from(document.getElementById("mapLayers").querySelectorAll("li:not(.buttonoff)")).map(node => node.id).sort();
|
||||
layersPreset.add(new Option(preset, preset, false, true));
|
||||
localStorage.setItem("presets", JSON.stringify(presets));
|
||||
localStorage.setItem("preset", preset);
|
||||
removePresetButton.style.display = "inline-block";
|
||||
savePresetButton.style.display = "none";
|
||||
}
|
||||
|
||||
function removePreset() {
|
||||
const preset = layersPreset.value;
|
||||
delete presets[preset];
|
||||
const index = Array.from(layersPreset.options).findIndex(o => o.value === preset);
|
||||
layersPreset.options.remove(index);
|
||||
layersPreset.value = "custom";
|
||||
removePresetButton.style.display = "none";
|
||||
savePresetButton.style.display = "inline-block";
|
||||
|
||||
localStorage.setItem("presets", JSON.stringify(presets));
|
||||
localStorage.removeItem("preset");
|
||||
}
|
||||
|
||||
function getCurrentPreset() {
|
||||
const layers = Array.from(document.getElementById("mapLayers").querySelectorAll("li:not(.buttonoff)")).map(node => node.id).sort();
|
||||
const defaultPresets = getDefaultPresets();
|
||||
|
||||
for (const preset in presets) {
|
||||
if (JSON.stringify(presets[preset]) !== JSON.stringify(layers)) continue;
|
||||
layersPreset.value = preset;
|
||||
removePresetButton.style.display = defaultPresets[preset] ? "none" : "inline-block";
|
||||
return;
|
||||
}
|
||||
|
||||
layersPreset.value = "custom";
|
||||
removePresetButton.style.display = "none";
|
||||
savePresetButton.style.display = "inline-block";
|
||||
}
|
||||
|
||||
function toggleHeight() {
|
||||
|
|
|
|||
|
|
@ -4,12 +4,6 @@
|
|||
$("#optionsContainer").draggable({handle: ".drag-trigger", snap: "svg", snapMode: "both"});
|
||||
$("#mapLayers").disableSelection();
|
||||
|
||||
// show control elements and remove loading screen on map load
|
||||
d3.select("#loading").transition().duration(5000).style("opacity", 0).remove();
|
||||
d3.select("#initial").transition().duration(5000).attr("opacity", 0).remove();
|
||||
d3.select("#optionsContainer").transition().duration(5000).style("opacity", 1);
|
||||
d3.select("#tooltip").transition().duration(5000).style("opacity", 1);
|
||||
|
||||
// remove glow if tip is aknowledged
|
||||
if (localStorage.getItem("disable_click_arrow_tooltip")) {
|
||||
clearMainTip();
|
||||
|
|
@ -677,6 +671,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;
|
||||
|
|
@ -722,7 +717,7 @@ function changeMapSize() {
|
|||
oceanPattern.select("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
|
||||
oceanLayers.select("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
|
||||
fitScaleBar();
|
||||
fitLegendBox();
|
||||
if (window.fitLegendBox) fitLegendBox();
|
||||
}
|
||||
|
||||
// just apply map size that was already set, apply graph size!
|
||||
|
|
@ -991,25 +986,31 @@ 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 === "quickSave") quickSave();
|
||||
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();
|
||||
else if (id === "saveDropbox") saveDropbox();
|
||||
if (id === "quickSave" || id === "saveMap" || id === "saveSVG" || id === "savePNG" || id === "saveDropbox") toggleSavePane();
|
||||
if (id === "loadMap") mapToLoad.click();
|
||||
else if (id === "quickLoad") quickLoad();
|
||||
else if (id === "loadURL") loadURL();
|
||||
else if (id === "loadDropbox") loadDropbox();
|
||||
if (id === "quickLoad" || id === "loadURL" || id === "loadMap" || id === "loadDropbox") toggleLoadPane();
|
||||
});
|
||||
|
||||
function regeneratePrompt() {
|
||||
if (customization) {tip("Please exit the customization mode first", false, "warning"); return;}
|
||||
const workingTime = (Date.now() - last(mapHistory).created) / 60000; // minutes
|
||||
if (workingTime < 15) {regenerateMap(); return;}
|
||||
if (workingTime < 10) {regenerateMap(); return;}
|
||||
|
||||
alertMessage.innerHTML = `Are you sure you want to generate a new map?<br>
|
||||
All unsaved changes made to the current map will be lost`;
|
||||
$("#alert").dialog({resizable: false, title: "Generate new map",
|
||||
buttons: {
|
||||
Cancel: function() {$(this).dialog("close");},
|
||||
Generate: regenerateMap
|
||||
Generate: function() {closeDialogs(); regenerateMap();}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1033,13 +1034,73 @@ function toggleSavePane() {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// async function saveDropbox() {
|
||||
// const filename = "fantasy_map_" + Date.now() + ".map";
|
||||
// const options = {
|
||||
// files: [{'url': '...', 'filename': 'fantasy_map.map'}],
|
||||
// success: function () {alert("Success! Files saved to your Dropbox.")},
|
||||
// progress: function (progress) {console.log(progress)},
|
||||
// cancel: function (cancel) {console.log(cancel)},
|
||||
// error: function (error) {console.log(error)}
|
||||
// };
|
||||
|
||||
// // working file: "https://dl.dropbox.com/s/llg93mwyonyzdmu/test.map?dl=1";
|
||||
// const dataBlob = await getMapData();
|
||||
// const URL = window.URL.createObjectURL(dataBlob);
|
||||
// Dropbox.save(URL, filename, options);
|
||||
// }
|
||||
|
||||
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:
|
||||
<input id="mapURL" type="url" style="width: 254px" placeholder="https://e-cloud.com/test.map">
|
||||
<br><i>Please note server should allow CORS for file to be loaded. If CORS is not allowed, save file to Dropbox and provide a direct link</i>`;
|
||||
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;}
|
||||
closeDialogs();
|
||||
loadMapFromURL(value);
|
||||
$(this).dialog("close");
|
||||
},
|
||||
Cancel: function() {$(this).dialog("close");}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// function loadDropbox() {
|
||||
// const options = {
|
||||
// success: function(file) {send_files(file)},
|
||||
// cancel: function() {},
|
||||
// linkType: "preview",
|
||||
// multiselect: false,
|
||||
// extensions:['.map'],
|
||||
// };
|
||||
// Dropbox.choose(options);
|
||||
|
||||
// function send_files(file) {
|
||||
// const subject = "Shared File Links";
|
||||
// let body = "";
|
||||
// for (let i=0; i < file.length; i++) {
|
||||
// body += file[i].name + "\n" + file[i].link + "\n\n";
|
||||
// }
|
||||
// location.href = 'mailto:coworker@example.com?Subject=' + escape(subject) + '&body='+ escape(body),'200','200';
|
||||
// }
|
||||
// }
|
||||
|
||||
// load map
|
||||
document.getElementById("mapToLoad").addEventListener("change", function() {
|
||||
closeDialogs();
|
||||
const fileToLoad = this.files[0];
|
||||
this.value = "";
|
||||
closeDialogs();
|
||||
uploadFile(fileToLoad);
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue