mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-22 12:01:23 +01:00
Adding gzip compression for improving storage use and backward compatibility. (#984)
* Basic gzip an gunzip on load and save. * refactor file save type to .gz and update the data in ui. --------- Co-authored-by: Azgaar <maxganiev@yandex.com>
This commit is contained in:
parent
5fba7d60f4
commit
ef24e3ea1a
10 changed files with 111 additions and 68 deletions
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
// update old .map version to the current one
|
||||
// update old .gz/.map version to the current one
|
||||
export function resolveVersionConflicts(version) {
|
||||
if (version < 1) {
|
||||
// v1.0 added a new religions layer
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"use strict";
|
||||
// Functions to load and parse .map files
|
||||
|
||||
// Functions to load and parse .gz/.map files
|
||||
async function quickLoad() {
|
||||
const blob = await ldb.get("lastMap");
|
||||
if (blob) loadMapPrompt(blob);
|
||||
|
|
@ -77,7 +76,7 @@ function loadMapPrompt(blob) {
|
|||
function loadMapFromURL(maplink, random) {
|
||||
const URL = decodeURIComponent(maplink);
|
||||
|
||||
fetch(URL, {method: "GET", mode: "cors"})
|
||||
fetch(URL, { method: "GET", mode: "cors" })
|
||||
.then(response => {
|
||||
if (response.ok) return response.blob();
|
||||
throw new Error("Cannot load map from URL");
|
||||
|
|
@ -91,9 +90,8 @@ function loadMapFromURL(maplink, random) {
|
|||
|
||||
function showUploadErrorMessage(error, URL, random) {
|
||||
ERROR && console.error(error);
|
||||
alertMessage.innerHTML = /* html */ `Cannot load map from the ${link(URL, "link provided")}. ${
|
||||
random ? `A new random map is generated. ` : ""
|
||||
} Please ensure the
|
||||
alertMessage.innerHTML = /* html */ `Cannot load map from the ${link(URL, "link provided")}. ${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",
|
||||
|
|
@ -112,11 +110,11 @@ function uploadMap(file, callback) {
|
|||
const currentVersion = parseFloat(version);
|
||||
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = function (fileLoadedEvent) {
|
||||
fileReader.onloadend = async function (fileLoadedEvent) {
|
||||
if (callback) callback();
|
||||
document.getElementById("coas").innerHTML = ""; // remove auto-generated emblems
|
||||
const result = fileLoadedEvent.target.result;
|
||||
const [mapData, mapVersion] = parseLoadedResult(result);
|
||||
const [mapData, mapVersion] = await parseLoadedResult(result);
|
||||
|
||||
const isInvalid = !mapData || isNaN(mapVersion) || mapData.length < 26 || !mapData[5];
|
||||
const isUpdated = mapVersion === currentVersion;
|
||||
|
|
@ -131,18 +129,37 @@ function uploadMap(file, callback) {
|
|||
if (isOutdated) return showUploadMessage("outdated", mapData, mapVersion);
|
||||
};
|
||||
|
||||
fileReader.readAsText(file, "UTF-8");
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
function parseLoadedResult(result) {
|
||||
async function uncompressMapData(compressedMapData) {
|
||||
console.log("trying to uncompress:", compressedMapData);
|
||||
try {
|
||||
const uncompressedStream = new Blob([compressedMapData]).stream().pipeThrough(new DecompressionStream("gzip"));
|
||||
let uncompressedData = [];
|
||||
for await (const chunk of uncompressedStream) {
|
||||
uncompressedData = uncompressedData.concat(Array.from(chunk));
|
||||
}
|
||||
return new Uint8Array(uncompressedData);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
async function parseLoadedResult(result) {
|
||||
try {
|
||||
const resultAsString = new TextDecoder().decode(result);
|
||||
// data can be in FMG internal format or base64 encoded
|
||||
const isDelimited = result.substr(0, 10).includes("|");
|
||||
const decoded = isDelimited ? result : decodeURIComponent(atob(result));
|
||||
const isDelimited = resultAsString.substring(0, 10).includes("|");
|
||||
const decoded = isDelimited ? resultAsString : decodeURIComponent(atob(resultAsString));
|
||||
const mapData = decoded.split("\r\n");
|
||||
const mapVersion = parseFloat(mapData[0].split("|")[0] || mapData[0]);
|
||||
return [mapData, mapVersion];
|
||||
} catch (error) {
|
||||
const uncompressedData = await uncompressMapData(result);
|
||||
if (uncompressedData !== null) {
|
||||
return parseLoadedResult(uncompressedData);
|
||||
}
|
||||
ERROR && console.error(error);
|
||||
return [null, null];
|
||||
}
|
||||
|
|
@ -153,7 +170,7 @@ function showUploadMessage(type, mapData, mapVersion) {
|
|||
let message, title, canBeLoaded;
|
||||
|
||||
if (type === "invalid") {
|
||||
message = `The file does not look like a valid <i>.map</i> file.<br>Please check the data format`;
|
||||
message = `The file does not look like a valid <i>.gz or .map</i> file.<br>Please check the data format`;
|
||||
title = "Invalid file";
|
||||
canBeLoaded = false;
|
||||
} else if (type === "ancient") {
|
||||
|
|
@ -177,7 +194,7 @@ function showUploadMessage(type, mapData, mapVersion) {
|
|||
if (canBeLoaded) parseLoadedData(mapData);
|
||||
}
|
||||
};
|
||||
$("#alert").dialog({title, buttons});
|
||||
$("#alert").dialog({ title, buttons });
|
||||
}
|
||||
|
||||
async function parseLoadedData(data) {
|
||||
|
|
@ -246,9 +263,9 @@ async function parseLoadedData(data) {
|
|||
if (data[34]) {
|
||||
const usedFonts = JSON.parse(data[34]);
|
||||
usedFonts.forEach(usedFont => {
|
||||
const {family: usedFamily, unicodeRange: usedRange, variant: usedVariant} = usedFont;
|
||||
const { family: usedFamily, unicodeRange: usedRange, variant: usedVariant } = usedFont;
|
||||
const defaultFont = fonts.find(
|
||||
({family, unicodeRange, variant}) =>
|
||||
({ family, unicodeRange, variant }) =>
|
||||
family === usedFamily && unicodeRange === usedRange && variant === usedVariant
|
||||
);
|
||||
if (!defaultFont) fonts.push(usedFont);
|
||||
|
|
@ -331,7 +348,7 @@ async function parseLoadedData(data) {
|
|||
void (function parseGridData() {
|
||||
grid = JSON.parse(data[6]);
|
||||
|
||||
const {cells, vertices} = calculateVoronoi(grid.points, grid.boundary);
|
||||
const { cells, vertices } = calculateVoronoi(grid.points, grid.boundary);
|
||||
grid.cells = cells;
|
||||
grid.vertices = vertices;
|
||||
|
||||
|
|
@ -349,7 +366,7 @@ async function parseLoadedData(data) {
|
|||
pack.cultures = JSON.parse(data[13]);
|
||||
pack.states = JSON.parse(data[14]);
|
||||
pack.burgs = JSON.parse(data[15]);
|
||||
pack.religions = data[29] ? JSON.parse(data[29]) : [{i: 0, name: "No religion"}];
|
||||
pack.religions = data[29] ? JSON.parse(data[29]) : [{ i: 0, name: "No religion" }];
|
||||
pack.provinces = data[30] ? JSON.parse(data[30]) : [0];
|
||||
pack.rivers = data[32] ? JSON.parse(data[32]) : [];
|
||||
pack.markers = data[35] ? JSON.parse(data[35]) : [];
|
||||
|
|
@ -375,7 +392,7 @@ async function parseLoadedData(data) {
|
|||
const e = d.split("|");
|
||||
if (!e.length) return;
|
||||
const b = e[5].split(",").length > 2 || !nameBases[i] ? e[5] : nameBases[i].b;
|
||||
nameBases[i] = {name: e[0], min: e[1], max: e[2], d: e[3], m: e[4], b};
|
||||
nameBases[i] = { name: e[0], min: e[1], max: e[2], d: e[3], m: e[4], b };
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
|
@ -624,7 +641,7 @@ async function parseLoadedData(data) {
|
|||
$(this).dialog("close");
|
||||
}
|
||||
},
|
||||
position: {my: "center", at: "center", of: "svg"}
|
||||
position: { my: "center", at: "center", of: "svg" }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"use strict";
|
||||
// functions to save project as .map file
|
||||
// functions to save project as .gz file
|
||||
|
||||
// prepare map data for saving
|
||||
function getMapData() {
|
||||
|
|
@ -116,18 +116,27 @@ function getMapData() {
|
|||
].join("\r\n");
|
||||
return mapData;
|
||||
}
|
||||
async function compressMapData(mapData){
|
||||
const compressedStream = new Blob([mapData]).stream().pipeThrough(new CompressionStream("gzip"));
|
||||
let compressedData = [];
|
||||
for await (const chunk of compressedStream){
|
||||
compressedData = compressedData.concat(Array.from(chunk));
|
||||
}
|
||||
return new Uint8Array(compressedData);
|
||||
}
|
||||
|
||||
// Download .map file
|
||||
function dowloadMap() {
|
||||
|
||||
// Download .gz file
|
||||
async function downloadMap() {
|
||||
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 mapData = await compressMapData(getMapData());
|
||||
const blob = new Blob([mapData], {type: "text/plain"});
|
||||
const URL = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.download = getFileName() + ".map";
|
||||
link.download = getFileName() + ".gz";
|
||||
link.href = URL;
|
||||
link.click();
|
||||
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
|
||||
|
|
@ -138,14 +147,14 @@ async function saveToDropbox() {
|
|||
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";
|
||||
const mapData = await compressMapData(getMapData());
|
||||
const filename = getFileName() + ".gz";
|
||||
try {
|
||||
await Cloud.providers.dropbox.save(filename, mapData);
|
||||
tip("Map is saved to your Dropbox", true, "success", 8000);
|
||||
} catch (msg) {
|
||||
ERROR && console.error(msg);
|
||||
tip("Cannot save .map to your Dropbox", true, "error", 8000);
|
||||
tip("Cannot save .gz to your Dropbox", true, "error", 8000);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +171,7 @@ async function initiateAutosave() {
|
|||
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 mapData = await compressMapData(getMapData());
|
||||
const blob = new Blob([mapData], {type: "text/plain"});
|
||||
await ldb.set("lastMap", blob);
|
||||
INFO && console.log("Autosaved at", new Date().toLocaleTimeString());
|
||||
|
|
@ -176,22 +185,22 @@ 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 mapData = await compressMapData(getMapData());
|
||||
const blob = new Blob([mapData], {type: "text/plain"});
|
||||
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);
|
||||
tip("Map is saved to browser memory. Please also save as .gz file to secure progress", true, "success", 2000);
|
||||
}
|
||||
|
||||
const saveReminder = function () {
|
||||
if (localStorage.getItem("noReminder")) return;
|
||||
const message = [
|
||||
"Please don't forget to save your work as a .map file",
|
||||
"Please remember to save work as a .map file",
|
||||
"Saving in .map format will ensure your data won't be lost in case of issues",
|
||||
"Please don't forget to save your work as a .gz file",
|
||||
"Please remember to save work as a .gz file",
|
||||
"Saving in .gz format will ensure your data won't be lost in case of issues",
|
||||
"Safety is number one priority. Please save the map",
|
||||
"Don't forget to save your map on a regular basis!",
|
||||
"Just a gentle reminder for you to save the map",
|
||||
"Please don't forget to save your progress (saving as .map is the best option)",
|
||||
"Please don't forget to save your progress (saving as .gz is the best option)",
|
||||
"Don't want to be reminded about need to save? Press CTRL+Q"
|
||||
];
|
||||
const interval = 15 * 60 * 1000; // remind every 15 minutes
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ function editHeightmap(options) {
|
|||
<p><i>Erase</i> mode also allows you Convert an Image into a heightmap or use Template Editor.</p>
|
||||
<p>You can <i>keep</i> the data, but you won't be able to change the coastline.</p>
|
||||
<p>Try <i>risk</i> mode to change the coastline and keep the data. The data will be restored as much as possible, but it can cause unpredictable errors.</p>
|
||||
<p>Please <span class="pseudoLink" onclick="dowloadMap();">save the map</span> before editing the heightmap!</p>
|
||||
<p>Please <span class="pseudoLink" onclick="downloadMap();">save the map</span> before editing the heightmap!</p>
|
||||
<p style="margin-bottom: 0">Check out ${link(
|
||||
"https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Heightmap-customization",
|
||||
"wiki"
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ function handleKeyup(event) {
|
|||
else if (code === "Delete") removeElementOnKey();
|
||||
else if (code === "KeyO" && document.getElementById("canvas3d")) toggle3dOptions();
|
||||
else if (ctrl && code === "KeyQ") toggleSaveReminder();
|
||||
else if (ctrl && code === "KeyS") dowloadMap();
|
||||
else if (ctrl && code === "KeyS") downloadMap();
|
||||
else if (ctrl && code === "KeyC") saveToDropbox();
|
||||
else if (ctrl && code === "KeyZ" && undo?.offsetParent) undo.click();
|
||||
else if (ctrl && code === "KeyY" && redo?.offsetParent) redo.click();
|
||||
|
|
|
|||
|
|
@ -844,8 +844,8 @@ async function connectToDropbox() {
|
|||
|
||||
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: 24em" placeholder="https://e-cloud.com/test.map">
|
||||
const inner = `Provide URL to a .gz or .map file:
|
||||
<input id="mapURL" type="url" style="width: 24em" placeholder="https://e-cloud.com/test.gz">
|
||||
<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({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue