mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
v1.0.09
This commit is contained in:
parent
baf23bee37
commit
e743735e57
11 changed files with 789 additions and 546 deletions
20
index.css
20
index.css
|
|
@ -996,7 +996,7 @@ div.slider .ui-slider-handle {
|
|||
top: 100%;
|
||||
border: 1px solid #5e4fa2;
|
||||
background-color: #a4879b;
|
||||
width: 3.6em;
|
||||
width: 5em;
|
||||
}
|
||||
|
||||
#loadDropdown {
|
||||
|
|
@ -1006,7 +1006,7 @@ div.slider .ui-slider-handle {
|
|||
top: 100%;
|
||||
border: 1px solid #5e4fa2;
|
||||
background-color: #a4879b;
|
||||
width: 6em;
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
#loadDropdown>div,
|
||||
|
|
@ -1510,6 +1510,7 @@ div.states > div.biomeArea {
|
|||
cursor: default;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#title_name {
|
||||
|
|
@ -1808,6 +1809,12 @@ svg.button {
|
|||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#info-line {
|
||||
font-size: .9em;
|
||||
color: gray;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.optionsSeedRestore {
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
|
|
@ -1922,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;
|
||||
|
|
|
|||
49
index.html
49
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>
|
||||
|
||||
|
|
@ -1819,18 +1832,20 @@
|
|||
</div>
|
||||
|
||||
<div id="sticked">
|
||||
<button id="newMapButton" data-tip="Generate a new map based on options. Shortcut: F7">New Map</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="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. Texture will not be shown. Shortcut: Ctrl + P">.png</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 disk</div>
|
||||
<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="zoomReset" data-tip="Reset map zoom. Shortcut: 0">Reset Zoom</button>
|
||||
|
|
@ -2006,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>
|
||||
|
|
@ -2022,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>
|
||||
|
|
@ -2952,8 +2967,8 @@
|
|||
<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>
|
||||
|
|
|
|||
282
main.js
282
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");
|
||||
|
|
@ -111,20 +117,58 @@ oceanPattern.append("rect").attr("fill", "url(#oceanic)").attr("x", 0).attr("y",
|
|||
oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
|
||||
|
||||
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);
|
||||
}()
|
||||
|
||||
// load linked map from url or generate a random map
|
||||
// decide which map should be loaded or generated on page load
|
||||
void function checkLoadParameters() {
|
||||
const url = new URL(window.location.href);
|
||||
const maplink = url.searchParams.get("maplink");
|
||||
const params = url.searchParams;
|
||||
|
||||
// check if URL is valid
|
||||
if (maplink) {
|
||||
// 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 a valid URL", maplink);
|
||||
if (valid) {loadMapFromURL(maplink, 1); return;}
|
||||
else showUploadErrorMessage("Map link is not a valid URL", maplink);
|
||||
}
|
||||
generateRandomMapOnLoad();
|
||||
|
||||
// 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) {
|
||||
|
|
@ -137,25 +181,104 @@ function loadMapFromURL(maplink, random) {
|
|||
}).then(blob => uploadFile(blob))
|
||||
.catch(error => {
|
||||
showUploadErrorMessage(error.message, URL, random);
|
||||
if (random) generateRandomMapOnLoad();
|
||||
if (random) generateMapOnLoad();
|
||||
});
|
||||
}
|
||||
|
||||
function showUploadErrorMessage(error, URL, random) {
|
||||
console.log(error);
|
||||
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`;
|
||||
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 generateRandomMapOnLoad() {
|
||||
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
|
||||
setTimeout(showWelcomeMessage, 7000); // show welcome message if required
|
||||
}
|
||||
|
||||
// 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() {
|
||||
|
|
@ -354,89 +477,7 @@ 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;
|
||||
|
||||
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 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>.
|
||||
|
||||
|
|
@ -471,16 +512,10 @@ function showWelcomeMessage() {
|
|||
|
||||
$("#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"}
|
||||
});
|
||||
}
|
||||
buttons: {OK: function() {$(this).dialog("close")}},
|
||||
position: {my: "center", at: "center", of: "svg"},
|
||||
close: () => localStorage.setItem("version", version)}
|
||||
);
|
||||
}
|
||||
|
||||
function zoomed() {
|
||||
|
|
@ -601,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");
|
||||
});
|
||||
|
|
@ -608,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();
|
||||
|
|
@ -646,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
|
||||
|
|
@ -1004,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;
|
||||
|
|
@ -1046,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
|
||||
|
|
@ -1429,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');
|
||||
}
|
||||
}
|
||||
|
|
@ -823,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},
|
||||
|
|
|
|||
|
|
@ -152,8 +152,9 @@ function GFontToDataURI(url) {
|
|||
}
|
||||
|
||||
// prepare map data for saving
|
||||
function getMapURL() {
|
||||
console.time("saveMap");
|
||||
function getMapData() {
|
||||
if (customization) return false;
|
||||
console.time("createMapDataBlob");
|
||||
|
||||
return new Promise(resolve => {
|
||||
const date = new Date();
|
||||
|
|
@ -192,33 +193,33 @@ function getMapURL() {
|
|||
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 URL = window.URL.createObjectURL(dataBlob);
|
||||
const blob = new Blob([data], {type: "text/plain"});
|
||||
|
||||
console.timeEnd("saveMap");
|
||||
resolve(URL);
|
||||
console.timeEnd("createMapDataBlob");
|
||||
resolve(blob);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Save in .map format
|
||||
// Download .map file
|
||||
async function saveMap() {
|
||||
if (customization) {tip("Map cannot be saved when is in edit mode, please exit the mode and retry", false, "error"); return;}
|
||||
if (customization) {tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error"); return;}
|
||||
closeDialogs();
|
||||
|
||||
const URL = await getMapURL();
|
||||
const blob = await getMapData();
|
||||
const URL = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.download = "fantasy_map_" + Date.now() + ".map";
|
||||
link.href = URL;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (crtl + 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);
|
||||
}
|
||||
|
||||
function uploadFile(file, callback) {
|
||||
console.time("loadMap");
|
||||
closeDialogs();
|
||||
uploadFile.timeStart = performance.now();
|
||||
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = function(fileLoadedEvent) {
|
||||
const dataLoaded = fileLoadedEvent.target.result;
|
||||
|
|
@ -252,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
|
||||
|
||||
|
|
@ -263,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]);
|
||||
|
|
@ -528,9 +531,108 @@ function parseLoadedData(data) {
|
|||
}()
|
||||
|
||||
changeMapSize();
|
||||
restoreDefaultEvents();
|
||||
if (window.restoreDefaultEvents) restoreDefaultEvents();
|
||||
invokeActiveZooming();
|
||||
tip("Map is successfully loaded", true, "success", 7000);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
@ -231,15 +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 === 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();
|
||||
|
|
@ -723,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!
|
||||
|
|
@ -994,28 +988,29 @@ document.getElementById("sticked").addEventListener("click", function(event) {
|
|||
else if (id === "saveButton") toggleSavePane();
|
||||
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");
|
||||
else if (id === "saveDropbox") saveDropbox();
|
||||
if (id === "saveMap" || id === "saveSVG" || id === "savePNG" || id === "saveDropbox") toggleSavePane();
|
||||
if (id === "loadMap") mapToLoad.click()
|
||||
if (id === "loadURL") loadURL();
|
||||
if (id === "loadDropbox") loadDropbox();
|
||||
if (id === "loadURL" || id === "loadMap" || id === "loadDropbox") toggleLoadPane();
|
||||
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();}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1052,7 +1047,8 @@ function toggleSavePane() {
|
|||
// };
|
||||
|
||||
// // working file: "https://dl.dropbox.com/s/llg93mwyonyzdmu/test.map?dl=1";
|
||||
// const URL = await getMapURL();
|
||||
// const dataBlob = await getMapData();
|
||||
// const URL = window.URL.createObjectURL(dataBlob);
|
||||
// Dropbox.save(URL, filename, options);
|
||||
// }
|
||||
|
||||
|
|
@ -1072,6 +1068,7 @@ function loadURL() {
|
|||
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");
|
||||
},
|
||||
|
|
@ -1104,5 +1101,6 @@ function loadURL() {
|
|||
document.getElementById("mapToLoad").addEventListener("change", function() {
|
||||
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