This commit is contained in:
Azgaar 2021-09-08 23:33:18 +03:00
commit 8dd7b3c8a9
6 changed files with 279 additions and 72 deletions

139
modules/cloud.js Normal file
View file

@ -0,0 +1,139 @@
"use strict";
/*
Cloud provider implementations (Dropbox only as now)
provider Interface:
name: name of the provider
async auth(): authenticate and get access tokens from provider
async save(filename): save map file to provider as filename
async load(filename): load filename from provider
async list(): list available filenames at provider
async getLink(filePath): get shareable link for file
restore(): restore access tokens from storage if possible
*/
window.Cloud = (function () {
// helpers to use in providers for token handling
const lSKey = x => `auth-${x}`;
const setToken = (prov, key) => localStorage.setItem(lSKey(prov), key);
const getToken = prov => localStorage.getItem(lSKey(prov));
/**********************************************************/
/* Dropbox provider */
/**********************************************************/
const DBP = {
name: "dropbox",
clientId: "pdr9ae64ip0qno4",
authWindow: undefined,
token: null, // Access token
api: null,
restore() {
this.token = getToken(this.name);
if (this.token) this.connect(this.token);
},
async call(name, param) {
try {
return await this.api[name](param);
} catch (e) {
if (e.name !== "DropboxResponseError") throw e;
// retry with auth
await this.auth();
return await this.api[name](param);
}
},
connect(token) {
const clientId = this.clientId;
const auth = new Dropbox.DropboxAuth({clientId});
auth.setAccessToken(token);
this.api = new Dropbox.Dropbox({auth});
},
async save(fileName, contents) {
if (!this.api) await this.auth();
const resp = this.call("filesUpload", {path: "/" + fileName, contents});
console.log("Dropbox response:", resp);
return true;
},
async load(path) {
if (!this.api) await this.auth();
const resp = await this.call("filesDownload", {path});
const blob = resp.result.fileBlob;
if (!blob) throw new Error("Invalid response from dropbox.");
return blob;
},
async list() {
if (!this.api) return null;
const resp = await this.call("filesListFolder", {path: ""});
return resp.result.entries.map(e => ({name: e.name, path: e.path_lower}));
},
auth() {
const url = window.location.origin + window.location.pathname + "dropbox.html";
this.authWindow = window.open(url, "auth", "width=640,height=480");
// child window expected to call
// window.opener.Cloud.providers.dropbox.setDropBoxToken (see below)
return new Promise((resolve, reject) => {
const watchDog = () => {
this.authWindow.close();
reject(new Error("Timeout. No auth for dropbox."));
};
setTimeout(watchDog, 120 * 1000);
window.addEventListener("dropboxauth", e => {
clearTimeout(watchDog);
resolve();
});
});
},
// Callback function for auth window.
setDropBoxToken(token) {
console.log("Access token got:", token);
setToken(this.name, token);
this.connect(token);
this.authWindow.close();
window.dispatchEvent(new Event("dropboxauth"));
},
async getLink(path) {
if (!this.api) await this.auth();
let resp;
// already exists?
resp = await this.call("sharingListSharedLinks", {path});
if (resp.result.links.length) return resp.result.links[0].url;
// create new
resp = await this.call("sharingCreateSharedLinkWithSettings", {
path,
settings: {
require_password: false,
audience: "public",
access: "viewer",
requested_visibility: "public",
allow_download: true
}
});
console.log("dropbox link object:", resp.result);
return resp.result.url;
}
};
// register providers here:
const providers = {
dropbox: DBP
};
// restore all providers at startup
for (const p of Object.values(providers)) p.restore();
return {providers};
})();

View file

@ -12,16 +12,35 @@ function quickLoad() {
});
}
function loadFromDropbox() {
const options = {
success: function (files) {
const url = files[0].link;
loadMapFromURL(url);
},
linkType: "direct",
extensions: [".map"]
};
Dropbox.choose(options);
async function loadFromDropbox() {
const mapPath = document.getElementById("loadFromDropboxSelect")?.value;
DEBUG && console.log("Loading map from Dropbox:", mapPath);
const blob = await Cloud.providers.dropbox.load(mapPath);
uploadMap(blob);
}
async function createSharableDropboxLink() {
const mapFile = document.querySelector("#loadFromDropbox select").value;
const sharableLink = document.getElementById("sharableLink");
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
let url;
try {
url = await Cloud.providers.dropbox.getLink(mapFile);
} catch {
tip("Dropbox API error. Can not create link.", true, "error", 2000);
return;
}
const fmg = window.location.href.split("?")[0];
const reallink = `${fmg}?maplink=${url}`;
// voodoo magic required by the yellow god of CORS
const link = reallink.replace("www.dropbox.com/s/", "dl.dropboxusercontent.com/1/view/");
const shortLink = link.slice(0, 50) + "...";
sharableLinkContainer.style.display = "block";
sharableLink.innerText = shortLink;
sharableLink.setAttribute("href", link);
}
function loadMapPrompt(blob) {

View file

@ -444,42 +444,19 @@ function dowloadMap() {
window.URL.revokeObjectURL(URL);
}
function saveToDropbox() {
async function saveToDropbox() {
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
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 URL = "data:text/plain; base64," + btoa(encodeURIComponent(mapData));
const filename = getFileName() + ".map";
const options = {
success: () => tip("Map is saved to your Dropbox", true, "success", 8000),
error: function (errorMessage) {
tip("Cannot save .map to your Dropbox", true, "error", 8000);
console.error(errorMessage);
}
};
Dropbox.save(URL, filename, options);
}
function createSharableDropboxLink() {
const sharableLink = document.getElementById("sharableLink");
const sharableLinkContainer = document.getElementById("sharableLinkContainer");
const options = {
success: function (files) {
const url = files[0].link;
const fmg = window.location.href.split("?")[0];
const link = `${fmg}/?maplink=${url}`;
const shortLink = link.slice(0, 50) + "...";
sharableLinkContainer.style.display = "block";
sharableLink.innerText = shortLink;
sharableLink.setAttribute("href", link);
},
linkType: "direct",
extensions: [".map"]
};
Dropbox.choose(options);
try {
await Cloud.providers.dropbox.save(filename, mapData);
tip("Map is saved to your Dropbox", true, "success", 8000);
} catch (msg) {
console.error(msg);
tip("Cannot save .map to your Dropbox", true, "error", 8000);
}
}
function saveGeoJSON_Cells() {

View file

@ -663,7 +663,7 @@ function showSavePane() {
$("#saveMapData").dialog({
title: "Save map",
resizable: false,
width: "27em",
width: "25em",
position: {my: "center", at: "center", of: "svg"},
buttons: {
Close: function () {
@ -695,11 +695,11 @@ function showExportPane() {
});
}
function showLoadPane() {
async function showLoadPane() {
$("#loadMapData").dialog({
title: "Load map",
resizable: false,
width: "22em",
width: "24em",
position: {my: "center", at: "center", of: "svg"},
buttons: {
Close: function () {
@ -707,6 +707,25 @@ function showLoadPane() {
}
}
});
const loadFromDropboxButtons = document.getElementById("loadFromDropboxButtons");
const fileSelect = document.getElementById("loadFromDropboxSelect");
const files = await Cloud.providers.dropbox.list();
if (!files) {
loadFromDropboxButtons.style.display = "none";
fileSelect.innerHTML = `<option value="" disabled selected>Save files to Dropbox first</option>`;
return;
}
loadFromDropboxButtons.style.display = "block";
fileSelect.innerHTML = "";
files.forEach(file => {
const opt = document.createElement("option");
opt.innerText = file.name;
opt.value = file.path;
fileSelect.appendChild(opt);
});
}
function loadURL() {