mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
feat: autosave v1.89.29
This commit is contained in:
parent
69e630b886
commit
d75ac3c99d
7 changed files with 172 additions and 102 deletions
52
index.html
52
index.html
|
|
@ -1690,18 +1690,6 @@
|
|||
Generator settings:
|
||||
</p>
|
||||
<table>
|
||||
<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)"
|
||||
>
|
||||
|
|
@ -1750,6 +1738,33 @@
|
|||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Set autosave interval in minutes. Set 0 to disable autosave. Map is saved to browser memory">
|
||||
<td></td>
|
||||
<td>Autosave interval</td>
|
||||
<td>
|
||||
<input
|
||||
id="autosaveIntervalInput"
|
||||
data-stored="autosaveInterval"
|
||||
type="range"
|
||||
min="0"
|
||||
max="60"
|
||||
step="1"
|
||||
value="15"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
id="autosaveIntervalOutput"
|
||||
data-stored="autosaveInterval"
|
||||
type="number"
|
||||
min="0"
|
||||
max="60"
|
||||
step="1"
|
||||
value="15"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr data-tip="Select speech synthesis voice to pronounce generated names">
|
||||
<td></td>
|
||||
<td>Speaker voice</td>
|
||||
|
|
@ -3239,7 +3254,7 @@
|
|||
style="margin-left: 0.1em"
|
||||
></i>
|
||||
</div>
|
||||
<iframe id="mfcgPreview" sandbox="allow-scripts allow-same-origin"></iframe>
|
||||
<iframe id="mfcgPreview" sandbox="allow-same-origin"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -5797,7 +5812,7 @@
|
|||
</button>
|
||||
<button
|
||||
onclick="quickSave()"
|
||||
data-tip="Save the project to browser storage. It can be unreliable"
|
||||
data-tip="Save the project to browser storage. It will overwrite the latest autosave"
|
||||
data-shortcut="F6"
|
||||
>
|
||||
browser
|
||||
|
|
@ -7826,9 +7841,10 @@
|
|||
<script src="libs/d3.min.js"></script>
|
||||
<script src="libs/priority-queue.min.js"></script>
|
||||
<script src="libs/delaunator.min.js"></script>
|
||||
<script src="libs/indexedDB.js"></script>
|
||||
|
||||
<script src="utils/shorthands.js"></script>
|
||||
<script src="utils/commonUtils.js"></script>
|
||||
<script src="utils/commonUtils.js?v=1.89.29"></script>
|
||||
<script src="utils/arrayUtils.js"></script>
|
||||
<script src="utils/colorUtils.js"></script>
|
||||
<script src="utils/graphUtils.js?v=1.88.02"></script>
|
||||
|
|
@ -7866,7 +7882,7 @@
|
|||
|
||||
<script src="modules/ui/general.js?v=1.87.03"></script>
|
||||
<script src="modules/ui/options.js?v=1.89.19"></script>
|
||||
<script src="main.js?v=1.89.19"></script>
|
||||
<script src="main.js?v=1.89.29"></script>
|
||||
|
||||
<script defer src="modules/relief-icons.js"></script>
|
||||
<script defer src="modules/ui/style.js"></script>
|
||||
|
|
@ -7908,8 +7924,8 @@
|
|||
<script defer src="libs/rgbquant.min.js"></script>
|
||||
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
|
||||
|
||||
<script defer src="modules/io/save.js"></script>
|
||||
<script defer src="modules/io/load.js?v=1.88.05"></script>
|
||||
<script defer src="modules/io/save.js?v=1.89.29"></script>
|
||||
<script defer src="modules/io/load.js?v=1.89.29"></script>
|
||||
<script defer src="modules/io/cloud.js"></script>
|
||||
<script defer src="modules/io/export.js?v=1.89.17"></script>
|
||||
<script defer src="modules/io/formats.js"></script>
|
||||
|
|
|
|||
82
libs/indexedDB.js
Normal file
82
libs/indexedDB.js
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
function waitForIndexedDB() {
|
||||
return new Promise(resolve => {
|
||||
const timer = setInterval(() => {
|
||||
if (window.indexedDB) {
|
||||
clearInterval(timer);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
function getValue(key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!window.indexedDB) {
|
||||
reject(new Error("indexedDB not supported"));
|
||||
return;
|
||||
}
|
||||
|
||||
waitForIndexedDB().then(() => {
|
||||
const request = window.indexedDB.open("d2", 1);
|
||||
|
||||
request.onsuccess = event => {
|
||||
const db = event.target.result;
|
||||
const transaction = db.transaction("s", "readonly");
|
||||
const objectStore = transaction.objectStore("s");
|
||||
const getRequest = objectStore.get(key);
|
||||
|
||||
getRequest.onsuccess = event => {
|
||||
const value = (event.target.result && event.target.result.v) || null;
|
||||
resolve(value);
|
||||
};
|
||||
|
||||
getRequest.onerror = event => {
|
||||
reject(new Error("indexedDB request error"));
|
||||
console.log(event);
|
||||
};
|
||||
};
|
||||
|
||||
request.onerror = event => {
|
||||
reject(new Error("indexedDB request error"));
|
||||
console.log(event);
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setValue(key, value) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!window.indexedDB) {
|
||||
reject(new Error("indexedDB not supported"));
|
||||
return;
|
||||
}
|
||||
|
||||
waitForIndexedDB().then(() => {
|
||||
const request = window.indexedDB.open("d2", 1);
|
||||
|
||||
request.onsuccess = event => {
|
||||
const db = event.target.result;
|
||||
const transaction = db.transaction("s", "readwrite");
|
||||
const objectStore = transaction.objectStore("s");
|
||||
|
||||
objectStore.put({k: key, v: value});
|
||||
|
||||
transaction.oncomplete = () => {
|
||||
resolve();
|
||||
};
|
||||
|
||||
transaction.onerror = event => {
|
||||
reject(new Error("indexedDB request error"));
|
||||
console.log(event);
|
||||
};
|
||||
};
|
||||
|
||||
request.onerror = event => {
|
||||
reject(new Error("indexedDB request error"));
|
||||
console.log(event);
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.ldb = {get: getValue, set: setValue};
|
||||
33
main.js
33
main.js
|
|
@ -221,8 +221,7 @@ oceanLayers
|
|||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
if (!location.hostname) {
|
||||
const wiki = "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Run-FMG-locally";
|
||||
alertMessage.innerHTML = /* html */ `Fantasy Map Generator cannot run serverless. Follow the <a href="${wiki}" target="_blank">instructions</a> on how you can
|
||||
easily run a local web-server`;
|
||||
alertMessage.innerHTML = /* html */ `Fantasy Map Generator cannot run serverless. Follow the <a href="${wiki}" target="_blank">instructions</a> on how you can easily run a local web-server`;
|
||||
|
||||
$("#alert").dialog({
|
||||
resizable: false,
|
||||
|
|
@ -240,6 +239,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||
await checkLoadParameters();
|
||||
}
|
||||
restoreDefaultEvents(); // apply default viewbox events
|
||||
initiateAutosave();
|
||||
});
|
||||
|
||||
function hideLoading() {
|
||||
|
|
@ -280,35 +280,20 @@ async function checkLoadParameters() {
|
|||
return;
|
||||
}
|
||||
|
||||
// open latest map if option is active and map is stored
|
||||
const loadLastMap = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
ldb.get("lastMap", blob => {
|
||||
if (blob) {
|
||||
WARN && console.warn("Load last saved map");
|
||||
try {
|
||||
uploadMap(blob);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
} else {
|
||||
reject("No map stored");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (onloadMap.value === "saved") {
|
||||
// check if there is a map saved to indexedDB
|
||||
const blob = await ldb.get("lastMap");
|
||||
if (blob) {
|
||||
try {
|
||||
await loadLastMap();
|
||||
WARN && console.warn("Loading last stored map");
|
||||
uploadMap(blob);
|
||||
} catch (error) {
|
||||
ERROR && console.error(error);
|
||||
WARN && console.warn("Cannot load stored map, random map to be generated");
|
||||
await generateMapOnLoad();
|
||||
generateMapOnLoad();
|
||||
}
|
||||
} else {
|
||||
WARN && console.warn("Generate random map");
|
||||
await generateMapOnLoad();
|
||||
generateMapOnLoad();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
"use strict";
|
||||
// Functions to load and parse .map files
|
||||
|
||||
function quickLoad() {
|
||||
ldb.get("lastMap", blob => {
|
||||
if (blob) {
|
||||
loadMapPrompt(blob);
|
||||
} else {
|
||||
tip("No map stored. Save map to storage first", true, "error", 2000);
|
||||
ERROR && console.error("No map stored");
|
||||
}
|
||||
});
|
||||
async function quickLoad() {
|
||||
const blob = ldb.get("lastMap");
|
||||
if (blob) loadMapPrompt(blob);
|
||||
else {
|
||||
tip("No map stored. Save map to browser storage first", true, "error", 2000);
|
||||
ERROR && console.error("No map stored");
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFromDropbox() {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
// prepare map data for saving
|
||||
function getMapData() {
|
||||
TIME && console.time("createMapData");
|
||||
|
||||
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";
|
||||
|
|
@ -116,13 +114,13 @@ function getMapData() {
|
|||
fonts,
|
||||
markers
|
||||
].join("\r\n");
|
||||
TIME && console.timeEnd("createMapData");
|
||||
return mapData;
|
||||
}
|
||||
|
||||
// Download .map file
|
||||
function dowloadMap() {
|
||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||
if (customization)
|
||||
return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||
closeDialogs("#alert");
|
||||
|
||||
const mapData = getMapData();
|
||||
|
|
@ -137,7 +135,8 @@ function dowloadMap() {
|
|||
}
|
||||
|
||||
async function saveToDropbox() {
|
||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||
if (customization)
|
||||
return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||
closeDialogs("#alert");
|
||||
const mapData = getMapData();
|
||||
const filename = getFileName() + ".map";
|
||||
|
|
@ -150,12 +149,36 @@ async function saveToDropbox() {
|
|||
}
|
||||
}
|
||||
|
||||
function quickSave() {
|
||||
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
|
||||
async function initiateAutosave() {
|
||||
const MINUTE = 60000; // munite in milliseconds
|
||||
let lastSavedAt = Date.now();
|
||||
|
||||
async function autosave() {
|
||||
const timeoutMinutes = byId("autosaveIntervalOutput").valueAsNumber;
|
||||
if (!timeoutMinutes) return;
|
||||
|
||||
const diffInMinutes = (Date.now() - lastSavedAt) / MINUTE;
|
||||
if (diffInMinutes < timeoutMinutes) return;
|
||||
if (customization) return tip("Autosave: map cannot be saved in edit mode", false, "warning", 2000);
|
||||
|
||||
tip("Autosave: saving map...", false, "warning", 3000);
|
||||
const mapData = getMapData();
|
||||
const blob = new Blob([mapData], {type: "text/plain"});
|
||||
await ldb.set("lastMap", blob);
|
||||
console.log("Autosaved at", new Date().toLocaleTimeString());
|
||||
lastSavedAt = Date.now();
|
||||
}
|
||||
|
||||
setInterval(autosave, MINUTE / 2);
|
||||
}
|
||||
|
||||
async function quickSave() {
|
||||
if (customization)
|
||||
return tip("Map cannot be saved when edit mode is active, please exit the mode first", false, "error");
|
||||
|
||||
const mapData = getMapData();
|
||||
const blob = new Blob([mapData], {type: "text/plain"});
|
||||
if (blob) ldb.set("lastMap", blob); // auto-save map
|
||||
await ldb.set("lastMap", blob); // auto-save map
|
||||
tip("Map is saved to browser memory. Please also save as .map file to secure progress", true, "success", 2000);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,11 @@ function isCtrlClick(event) {
|
|||
}
|
||||
|
||||
function generateDate(from = 100, to = 1000) {
|
||||
return new Date(rand(from, to), rand(12), rand(31)).toLocaleDateString("en", {year: "numeric", month: "long", day: "numeric"});
|
||||
return new Date(rand(from, to), rand(12), rand(31)).toLocaleDateString("en", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric"
|
||||
});
|
||||
}
|
||||
|
||||
function getLongitude(x, decimals = 2) {
|
||||
|
|
@ -158,7 +162,8 @@ void (function () {
|
|||
const defaultOptions = {default: 1, step: 0.01, min: 0, max: 100, required: true};
|
||||
|
||||
window.prompt = function (promptText = defaultText, options = defaultOptions, callback) {
|
||||
if (options.default === undefined) return ERROR && console.error("Prompt: options object does not have default value defined");
|
||||
if (options.default === undefined)
|
||||
return ERROR && console.error("Prompt: options object does not have default value defined");
|
||||
|
||||
const input = prompt.querySelector("#promptInput");
|
||||
prompt.querySelector("#promptText").innerHTML = promptText;
|
||||
|
|
@ -192,41 +197,3 @@ void (function () {
|
|||
prompt.style.display = "none";
|
||||
});
|
||||
})();
|
||||
|
||||
// indexedDB; ldb object
|
||||
void (function () {
|
||||
function e(t, o) {
|
||||
return n
|
||||
? void (n.transaction("s").objectStore("s").get(t).onsuccess = function (e) {
|
||||
var t = (e.target.result && e.target.result.v) || null;
|
||||
o(t);
|
||||
})
|
||||
: void setTimeout(function () {
|
||||
e(t, o);
|
||||
}, 100);
|
||||
}
|
||||
var t = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||
if (!t) return void ERROR && console.error("indexedDB not supported");
|
||||
var n,
|
||||
o = {k: "", v: ""},
|
||||
r = t.open("d2", 1);
|
||||
(r.onsuccess = function (e) {
|
||||
n = this.result;
|
||||
}),
|
||||
(r.onerror = function (e) {
|
||||
ERROR && console.error("indexedDB request error"), INFO && console.log(e);
|
||||
}),
|
||||
(r.onupgradeneeded = function (e) {
|
||||
n = null;
|
||||
var t = e.target.result.createObjectStore("s", {keyPath: "k"});
|
||||
t.transaction.oncomplete = function (e) {
|
||||
n = e.target.db;
|
||||
};
|
||||
}),
|
||||
(window.ldb = {
|
||||
get: e,
|
||||
set: function (e, t) {
|
||||
(o.k = e), (o.v = t), n.transaction("s", "readwrite").objectStore("s").put(o);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
// version and caching control
|
||||
const version = "1.89.28"; // generator version, update each time
|
||||
const version = "1.89.29"; // generator version, update each time
|
||||
|
||||
{
|
||||
document.title += " v" + version;
|
||||
|
|
@ -28,6 +28,7 @@ const version = "1.89.28"; // generator version, update each time
|
|||
|
||||
<ul>
|
||||
<strong>Latest changes:</strong>
|
||||
<li>Autosave feature (in Options)</li>
|
||||
<li>Google translation support (in Options)</li>
|
||||
<li>Religions can be edited and redrawn like cultures</li>
|
||||
<li>Lock states, provinces, cultures, and religions from regeneration</li>
|
||||
|
|
@ -35,8 +36,6 @@ const version = "1.89.28"; // generator version, update each time
|
|||
<li>Data Charts screen</li>
|
||||
<li>Сultures and religions can have multiple parents in hierarchy tree</li>
|
||||
<li>Heightmap selection screen</li>
|
||||
<li>Dialogs optimization for mobile</li>
|
||||
<li>New heightmap template: Fractious</li>
|
||||
</ul>
|
||||
|
||||
<p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue