This commit is contained in:
Azgaar 2021-09-08 09:50:46 +03:00
commit 6da81efeb2
6 changed files with 256 additions and 57 deletions

46
dropbox.html Normal file
View file

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<script type="text/javascript" src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script>
<title>FMG Dropbox Auth</title>
</head>
<body>
<script>
/*
open this page in a new window without query parameter to start auth
window.opener.setDropBoxToken(token) will be called on the opener
window.
*/
const REDIRECT_URI = window.location.origin + window.location.pathname;
const dbxAuth = new Dropbox.DropboxAuth({ clientId: 'sp7tzwm27u2w5ns', });
const spObj = new URLSearchParams(window.location.search);
const searchParams = Object.fromEntries(spObj.entries())
if (searchParams.code) getToken()
else doAuth(); // start authentication
function doAuth() {
dbxAuth.getAuthenticationUrl(REDIRECT_URI, undefined, 'code', 'offline', undefined, undefined, true)
.then(authUrl => {
window.sessionStorage.clear();
window.sessionStorage.setItem("codeVerifier", dbxAuth.codeVerifier);
window.location.href = authUrl;
})
.catch((error) => console.error(error));
};
function getToken() {
dbxAuth.setCodeVerifier(window.sessionStorage.getItem('codeVerifier'));
dbxAuth.getAccessTokenFromCode(REDIRECT_URI, searchParams.code)
.then((resp) => {
const token = resp.result.access_token;
window.opener.Cloud.providers.dropbox.setDropBoxToken(token)
}).catch((error) => {
console.error(error)
});
}
</script>
</body>
</html>

View file

@ -1995,7 +1995,7 @@
<div data-tip="Burg mean annual temperature and real-world city for comparison">
<div class="label">Temperature:</div>
<span id="burgTemperature"></span>, like in
<span id="burgTemperature"></span>, like in
<span id="burgTemperatureLikeIn"></span>
</div>
@ -3490,26 +3490,29 @@
<button onclick="quickSave()" data-tip="Save the project to browser storage (quick save). It can be unreliable. Shortcut: F6">browser</button>
</div>
<p style="font-style: italic">Maps are saved in <i>.map</i> format, that can be loaded back via 'Load' in menu. Please keep noted that we do not keep any data on our side. There is no way to restore the progress if .map file is lost. Please keep old .map files on your machine or cloud storage as backups.</p>
<p style="font-style: italic">Saving to Dropbox may not be allowed for big files. In this case open <a href="https://www.dropbox.com/home/FMG" target="_blank">Dropbox</a>, download the .map file and upload it manually.</p>
<div style="margin-top: .3em" data-tip="Select .map file on dropbox and share a sharable link">
<strong>Create sharable link</strong>
<button onclick="createSharableDropboxLink()">select file</button>
<div id="sharableLinkContainer" style="display: none">
<a id="sharableLink" target="_blank"></a>
<i data-tip="Copy link to the clipboard" onclick="copyLinkToClickboard()" class="icon-clone pointer"></i>
</div>
</div>
</div>
<div id="loadMapData" style="display: none" class="dialog">
<div style="margin-bottom: .3em">Load map from</div>
<div>
<button onclick="mapToLoad.click()" data-tip="Load .map file from local disk">local disk</button>
<button onclick="loadFromDropbox()" data-tip="Load .map file from your Dropbox">Dropbox</button>
<button onclick="loadURL()" data-tip="Load .map file from URL (server should allow CORS)">URL</button>
<button onclick="quickLoad()" data-tip="Load map from browser storage (if saved before)">storage</button>
</div>
<div id="loadFromDropbox">
<p>From your Dropbox account:</p>
<select style="margin-bottom:.3em">
</select>
<button onclick="loadFromDropbox()" data-tip="Load .map file from your Dropbox">Open</button>
<button onclick="createSharableDropboxLink()" data-tip="Select .map file on dropbox and share a sharable link">Create link</button>
<div style="margin-top: .3em">
<div id="sharableLinkContainer" style="display: none">
<a id="sharableLink" target="_blank"></a>
<i data-tip="Copy link to the clipboard" onclick="copyLinkToClickboard()" class="icon-clone pointer"></i>
</div>
</div>
</div>
</div>
<div id="saveTilesScreen" style="display: none" class="dialog">
@ -3527,7 +3530,7 @@
<div data-tip="Image scale relative to image size (e.g. 5x)" style="margin-bottom: .3em">
<div class="label">Scale:</div>
<input id="tileScaleInput" data-stored="tileScale" type="range" min=1 max=4 value=1 style="width: 11em">
<input id="tileScaleOutput" data-stored="tileScale" type="number" min=1 value=1
<input id="tileScaleOutput" data-stored="tileScale" type="number" min=1 value=1
>
</div>
<div data-tip="Calculated size of image if combined" style="margin-bottom: .3em">
@ -4250,11 +4253,13 @@
<script src="modules/ui/layers.js"></script>
<script src="modules/ui/measurers.js"></script>
<script defer src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script>
<script defer src="modules/ui/general.js"></script>
<script defer src="modules/ui/options.js"></script>
<script defer src="modules/ui/style.js"></script>
<script defer src="modules/save.js"></script>
<script defer src="modules/load.js"></script>
<script defer src="modules/cloud.js"></script>
<script defer src="main.js"></script>
<script defer src="modules/relief-icons.js"></script>
<script defer src="modules/ui/tools.js"></script>
@ -4295,6 +4300,5 @@
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
<script defer src="libs/pell.min.js"></script>
<script defer src="libs/jszip.min.js"></script>
<script defer src="libs/dropins.min.js" id="dropboxjs" data-app-key="pdr9ae64ip0qno4"></script>
</body>
</html>

141
modules/cloud.js Normal file
View file

@ -0,0 +1,141 @@
"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: 'sp7tzwm27u2w5ns',
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,34 @@ 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(fileName) {
const map = document.querySelector("#loadFromDropbox select").value;
console.log('loading dropbox map', map);
const blob = await Cloud.providers.dropbox.load(map);
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

@ -435,42 +435,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

@ -695,7 +695,7 @@ function showExportPane() {
});
}
function showLoadPane() {
async function showLoadPane() {
$("#loadMapData").dialog({
title: "Load map",
resizable: false,
@ -707,6 +707,19 @@ function showLoadPane() {
}
}
});
const dpx = document.getElementById("loadFromDropbox");
const dpf = dpx.querySelector("select");
const files = await Cloud.providers.dropbox.list();
dpx.style.display = files? "block" : "none";
if (!files) return;
while(dpf.firstChild) dpf.removeChild(dpf.firstChild);
files.forEach(f => {
const opt = document.createElement('option');
opt.innerText = f.name;
opt.value = f.path;
dpf.appendChild(opt);
});
}
function loadURL() {