mapshaper 0.6.18 → 0.6.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mapshaper.js +2650 -6497
- package/package.json +4 -3
- package/www/index.html +10 -7
- package/www/mapshaper-gui.js +603 -241
- package/www/mapshaper.js +2650 -6497
- package/www/modules.js +301 -1
- package/www/page.css +79 -7
package/www/mapshaper-gui.js
CHANGED
|
@@ -1106,6 +1106,590 @@
|
|
|
1106
1106
|
|
|
1107
1107
|
// utils.inherit(SimpleButton, EventDispatcher);
|
|
1108
1108
|
|
|
1109
|
+
function showPopupAlert(msg, title) {
|
|
1110
|
+
var self = {}, html = '';
|
|
1111
|
+
var _cancel, _close;
|
|
1112
|
+
var warningRxp = /^Warning: /;
|
|
1113
|
+
var el = El('div').appendTo('body').addClass('error-wrapper');
|
|
1114
|
+
var infoBox = El('div').appendTo(el).addClass('error-box info-box selectable');
|
|
1115
|
+
if (!title && warningRxp.test(msg)) {
|
|
1116
|
+
title = 'Warning';
|
|
1117
|
+
msg = msg.replace(warningRxp, '');
|
|
1118
|
+
}
|
|
1119
|
+
if (title) {
|
|
1120
|
+
html += `<div class="error-title">${title}</div>`;
|
|
1121
|
+
}
|
|
1122
|
+
html += `<p class="error-message">${msg}</p>`;
|
|
1123
|
+
El('div').appendTo(infoBox).addClass('close2-btn').on('click', function() {
|
|
1124
|
+
if (_cancel) _cancel();
|
|
1125
|
+
self.close();
|
|
1126
|
+
});
|
|
1127
|
+
El('div').appendTo(infoBox).addClass('error-content').html(html);
|
|
1128
|
+
|
|
1129
|
+
self.onCancel = function(cb) {
|
|
1130
|
+
_cancel = cb;
|
|
1131
|
+
return self;
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
self.onClose = function(cb) {
|
|
1135
|
+
_close = cb;
|
|
1136
|
+
return self;
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
self.button = function(label, cb) {
|
|
1140
|
+
El('div')
|
|
1141
|
+
.addClass("btn dialog-btn alert-btn")
|
|
1142
|
+
.appendTo(infoBox)
|
|
1143
|
+
.html(label)
|
|
1144
|
+
.on('click', function() {
|
|
1145
|
+
self.close();
|
|
1146
|
+
cb();
|
|
1147
|
+
});
|
|
1148
|
+
return self;
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
self.close = function() {
|
|
1152
|
+
if (el) el.remove();
|
|
1153
|
+
if (_close) _close();
|
|
1154
|
+
el = _cancel = _close = null;
|
|
1155
|
+
};
|
|
1156
|
+
return self;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function AlertControl(gui) {
|
|
1160
|
+
var openAlert; // error popup
|
|
1161
|
+
var openPopup; // any popup
|
|
1162
|
+
|
|
1163
|
+
gui.addMode('alert', function() {}, closePopup);
|
|
1164
|
+
|
|
1165
|
+
gui.alert = function(str, title) {
|
|
1166
|
+
closePopup();
|
|
1167
|
+
openAlert = openPopup = showPopupAlert(str, title);
|
|
1168
|
+
// alert.button('close', gui.clearMode);
|
|
1169
|
+
openAlert.onClose(gui.clearMode);
|
|
1170
|
+
gui.enterMode('alert');
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
gui.message = function(str, title) {
|
|
1174
|
+
if (openPopup) return; // don't stomp on another popup
|
|
1175
|
+
openPopup = showPopupAlert(str, title);
|
|
1176
|
+
openPopup.onClose(function() {openPopup = null;});
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
function closePopup() {
|
|
1180
|
+
if (openPopup) openPopup.close();
|
|
1181
|
+
openPopup = openAlert = null;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function saveZipFile(zipfileName, files, done) {
|
|
1186
|
+
internal.zipAsync(files, function(err, buf) {
|
|
1187
|
+
if (err) {
|
|
1188
|
+
done(errorMessage(err));
|
|
1189
|
+
} else {
|
|
1190
|
+
saveBlobToLocalFile(zipfileName, new Blob([buf]), done);
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
function errorMessage(err) {
|
|
1195
|
+
var str = "Error creating Zip file";
|
|
1196
|
+
if (err.message) {
|
|
1197
|
+
str += ": " + err.message;
|
|
1198
|
+
}
|
|
1199
|
+
return str;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function saveFilesToServer(paths, data, done) {
|
|
1204
|
+
var i = -1;
|
|
1205
|
+
next();
|
|
1206
|
+
function next(err) {
|
|
1207
|
+
i++;
|
|
1208
|
+
if (err) return done(err);
|
|
1209
|
+
if (i >= data.length) return done();
|
|
1210
|
+
saveBlobToServer(paths[i], new Blob([data[i]]), next);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function saveBlobToServer(path, blob, done) {
|
|
1215
|
+
var q = '?file=' + encodeURIComponent(path);
|
|
1216
|
+
var url = window.location.origin + '/save' + q;
|
|
1217
|
+
window.fetch(url, {
|
|
1218
|
+
method: 'POST',
|
|
1219
|
+
credentials: 'include',
|
|
1220
|
+
body: blob
|
|
1221
|
+
}).then(function(resp) {
|
|
1222
|
+
if (resp.status == 400) {
|
|
1223
|
+
return resp.text();
|
|
1224
|
+
}
|
|
1225
|
+
}).then(function(err) {
|
|
1226
|
+
done(err);
|
|
1227
|
+
}).catch(function(resp) {
|
|
1228
|
+
done('connection to server was lost');
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
async function saveBlobToLocalFile(filename, blob, done) {
|
|
1233
|
+
done = done || function() {};
|
|
1234
|
+
if (window.showSaveFilePicker) {
|
|
1235
|
+
saveBlobToSelectedFile(filename, blob, done);
|
|
1236
|
+
} else {
|
|
1237
|
+
saveBlobToDownloadsFolder(filename, blob, done);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
function showSaveDialog(filename, blob, done) {
|
|
1242
|
+
showPopupAlert(`Save ${filename} to:`)
|
|
1243
|
+
.button('selected folder', function() {
|
|
1244
|
+
saveBlobToSelectedFile(filename, blob, done);
|
|
1245
|
+
})
|
|
1246
|
+
.button('downloads', function() {
|
|
1247
|
+
saveBlobToDownloadsFolder(filename, blob, done);
|
|
1248
|
+
})
|
|
1249
|
+
.onCancel(done);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
async function saveBlobToSelectedFile(filename, blob, done) {
|
|
1253
|
+
// see: https://developer.chrome.com/articles/file-system-access/
|
|
1254
|
+
// note: saving multiple files to a directory using showDirectoryPicker()
|
|
1255
|
+
// does not work well (in Chrome). User is prompted for permission each time,
|
|
1256
|
+
// and some directories (like Downloads and Documents) are blocked.
|
|
1257
|
+
//
|
|
1258
|
+
var options = getSaveFileOptions(filename);
|
|
1259
|
+
var handle;
|
|
1260
|
+
try {
|
|
1261
|
+
handle = await window.showSaveFilePicker(options);
|
|
1262
|
+
var writable = await handle.createWritable();
|
|
1263
|
+
await writable.write(blob);
|
|
1264
|
+
await writable.close();
|
|
1265
|
+
} catch(e) {
|
|
1266
|
+
if (e.name == 'SecurityError') {
|
|
1267
|
+
// assuming this is a timeout error, with message like:
|
|
1268
|
+
// "Must be handling a user gesture to show a file picker."
|
|
1269
|
+
showSaveDialog(filename, blob, done);
|
|
1270
|
+
} else if (e.name == 'AbortError') {
|
|
1271
|
+
// fired if user clicks a cancel button (normal, no error message)
|
|
1272
|
+
// BUT: this kind of erro rmay also get fired when saving to a protected folder
|
|
1273
|
+
// (should show error message)
|
|
1274
|
+
done();
|
|
1275
|
+
} else {
|
|
1276
|
+
console.error(e.name, e.message, e);
|
|
1277
|
+
done('Save failed for an unknown reason');
|
|
1278
|
+
}
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
done();
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function getSaveFileOptions(filename) {
|
|
1286
|
+
// see: https://wicg.github.io/file-system-access/#api-filepickeroptions
|
|
1287
|
+
var type = internal.guessInputFileType(filename);
|
|
1288
|
+
var ext = internal.getFileExtension(filename).toLowerCase();
|
|
1289
|
+
var accept = {};
|
|
1290
|
+
if (ext == 'kml') {
|
|
1291
|
+
accept['application/vnd.google-earth.kml+xml'] = ['.kml'];
|
|
1292
|
+
} else if (ext == 'svg') {
|
|
1293
|
+
accept['image/svg+xml'] = ['.svg'];
|
|
1294
|
+
} else if (ext == 'zip') {
|
|
1295
|
+
accept['application/zip'] == ['.zip'];
|
|
1296
|
+
} else if (type == 'text') {
|
|
1297
|
+
accept['text/csv'] = ['.csv', '.tsv', '.tab', '.txt'];
|
|
1298
|
+
} else if (type == 'json') {
|
|
1299
|
+
accept['application/json'] = ['.json', '.geojson', '.topojson'];
|
|
1300
|
+
} else {
|
|
1301
|
+
accept['application/octet-stream'] = ['.' + ext];
|
|
1302
|
+
}
|
|
1303
|
+
return {
|
|
1304
|
+
suggestedName: filename,
|
|
1305
|
+
// If startIn is given, Chrome will always start there
|
|
1306
|
+
// Default is to start in the previously selected dir (better)
|
|
1307
|
+
// // startIn: 'downloads', // or: desktop, documents, [file handle], [directory handle]
|
|
1308
|
+
types: [{
|
|
1309
|
+
description: 'Files',
|
|
1310
|
+
accept: accept
|
|
1311
|
+
}]
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
function saveBlobToDownloadsFolder(filename, blob, done) {
|
|
1317
|
+
var anchor, blobUrl;
|
|
1318
|
+
try {
|
|
1319
|
+
blobUrl = URL.createObjectURL(blob);
|
|
1320
|
+
} catch(e) {
|
|
1321
|
+
done("Mapshaper can't export files from this browser. Try switching to Chrome or Firefox.");
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
anchor = El('a').attr('href', '#').appendTo('body').node();
|
|
1325
|
+
anchor.href = blobUrl;
|
|
1326
|
+
anchor.download = filename;
|
|
1327
|
+
var clickEvent = document.createEvent("MouseEvent");
|
|
1328
|
+
clickEvent.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false,
|
|
1329
|
+
false, false, false, 0, null);
|
|
1330
|
+
anchor.dispatchEvent(clickEvent);
|
|
1331
|
+
setTimeout(function() {
|
|
1332
|
+
// Revoke blob url to release memory; timeout needed in firefox
|
|
1333
|
+
URL.revokeObjectURL(blobUrl);
|
|
1334
|
+
anchor.parentNode.removeChild(anchor);
|
|
1335
|
+
done();
|
|
1336
|
+
}, 400);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
var idb = require('idb-keyval');
|
|
1340
|
+
// https://github.com/jakearchibald/idb
|
|
1341
|
+
// https://github.com/jakearchibald/idb-keyval
|
|
1342
|
+
var sessionId = getUniqId('session');
|
|
1343
|
+
var snapshotCount = 0;
|
|
1344
|
+
|
|
1345
|
+
window.addEventListener('beforeunload', async function() {
|
|
1346
|
+
// delete snapshot data
|
|
1347
|
+
// This is not ideal, because the data gets deleted even if the user
|
|
1348
|
+
// cancels the page close... but there's no apparent good alternative
|
|
1349
|
+
await finalCleanup();
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
function getUniqId(prefix) {
|
|
1353
|
+
return prefix + '_' + (Math.random() + 1).toString(36).substring(2,8);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function isSnapshotId(str) {
|
|
1357
|
+
return /^session_/.test(str);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
function SessionSnapshots(gui) {
|
|
1361
|
+
if (!gui.options.saveControl) return;
|
|
1362
|
+
var _menuOpen = false;
|
|
1363
|
+
var _menuTimeout;
|
|
1364
|
+
var btn = gui.buttons.addButton('#ribbon-icon').addClass('menu-btn save-btn');
|
|
1365
|
+
var menu = El('div').addClass('nav-sub-menu save-menu').appendTo(btn.node());
|
|
1366
|
+
|
|
1367
|
+
initialCleanup();
|
|
1368
|
+
|
|
1369
|
+
btn.on('mouseenter', function() {
|
|
1370
|
+
btn.addClass('hover');
|
|
1371
|
+
clearTimeout(_menuTimeout); // prevent timed closing
|
|
1372
|
+
if (!_menuOpen) {
|
|
1373
|
+
openMenu();
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
btn.on('mouseleave', function() {
|
|
1378
|
+
if (!_menuOpen) {
|
|
1379
|
+
btn.removeClass('hover');
|
|
1380
|
+
} else {
|
|
1381
|
+
closeMenu(200);
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
async function renderMenu() {
|
|
1386
|
+
var snapshots = await fetchSnapshotList();
|
|
1387
|
+
|
|
1388
|
+
menu.empty();
|
|
1389
|
+
addMenuLink({
|
|
1390
|
+
slug: 'stash',
|
|
1391
|
+
// label: 'save data snapshot',
|
|
1392
|
+
label: 'take a snapshot',
|
|
1393
|
+
action: saveSnapshot
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
// var available = await getAvailableStorage();
|
|
1397
|
+
// if (available) {
|
|
1398
|
+
// El('div').addClass('save-menu-entry').text(available + ' available').appendTo(menu);
|
|
1399
|
+
// }
|
|
1400
|
+
|
|
1401
|
+
// if (snapshots.length > 0) {
|
|
1402
|
+
// El('div').addClass('save-menu-entry').text('snapshots').appendTo(menu);
|
|
1403
|
+
// }
|
|
1404
|
+
|
|
1405
|
+
snapshots.forEach(function(item, i) {
|
|
1406
|
+
var id = i + 1;
|
|
1407
|
+
var line = El('div').appendTo(menu).addClass('save-menu-item');
|
|
1408
|
+
El('span').appendTo(line).html(`<span class="save-item-label">#${item.number}</span> `);
|
|
1409
|
+
// show snapshot size
|
|
1410
|
+
// El('span').appendTo(line).html(` <span class="save-item-size">${item.display_size}</span>`);
|
|
1411
|
+
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1412
|
+
await restoreSnapshotById(item.id, gui);
|
|
1413
|
+
closeMenu(100);
|
|
1414
|
+
}).text('restore');
|
|
1415
|
+
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1416
|
+
var buf = await idb.get(item.id);
|
|
1417
|
+
saveBlobToLocalFile('mapshaper_snapshot.msx', new Blob([buf]));
|
|
1418
|
+
}).text('export');
|
|
1419
|
+
El('span').addClass('save-menu-btn').appendTo(line).on('click', async function(e) {
|
|
1420
|
+
await removeSnapshotById(item.id);
|
|
1421
|
+
closeMenu(300);
|
|
1422
|
+
renderMenu();
|
|
1423
|
+
}).text('remove');
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
// if (snapshots.length >= 1) {
|
|
1427
|
+
// addMenuLink({
|
|
1428
|
+
// slug: 'clear',
|
|
1429
|
+
// label: 'remove all',
|
|
1430
|
+
// action: clearData
|
|
1431
|
+
// });
|
|
1432
|
+
// }
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function addMenuLink(item) {
|
|
1436
|
+
var line = El('div').appendTo(menu);
|
|
1437
|
+
var link = El('div').addClass('save-menu-link save-menu-entry').attr('data-name', item.slug).text(item.label).appendTo(line);
|
|
1438
|
+
link.on('click', async function(e) {
|
|
1439
|
+
await item.action(gui);
|
|
1440
|
+
e.stopPropagation();
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
function openMenu() {
|
|
1445
|
+
clearTimeout(_menuTimeout);
|
|
1446
|
+
if (!_menuOpen) {
|
|
1447
|
+
btn.addClass('open');
|
|
1448
|
+
_menuOpen = true;
|
|
1449
|
+
renderMenu();
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
function closeMenu(delay) {
|
|
1454
|
+
if (!_menuOpen) return;
|
|
1455
|
+
clearTimeout(_menuTimeout);
|
|
1456
|
+
_menuTimeout = setTimeout(function() {
|
|
1457
|
+
_menuOpen = false;
|
|
1458
|
+
btn.removeClass('open');
|
|
1459
|
+
btn.removeClass('hover');
|
|
1460
|
+
}, delay || 0);
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
async function saveSnapshot(gui) {
|
|
1464
|
+
var buf = captureSnapshot(gui);
|
|
1465
|
+
var entryId = String(++snapshotCount).padStart(3, '0');
|
|
1466
|
+
var snapshotId = sessionId + '_' + entryId; // e.g. session_d89fw_001
|
|
1467
|
+
var entry = {
|
|
1468
|
+
created: Date.now(),
|
|
1469
|
+
session: sessionId,
|
|
1470
|
+
id: snapshotId,
|
|
1471
|
+
name: snapshotCount + '.',
|
|
1472
|
+
number: snapshotCount,
|
|
1473
|
+
size: buf.length,
|
|
1474
|
+
display_size: formatSize(buf.length)
|
|
1475
|
+
};
|
|
1476
|
+
|
|
1477
|
+
await idb.set(entry.id, buf);
|
|
1478
|
+
await addToIndex(entry);
|
|
1479
|
+
renderMenu();
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
function formatSize(bytes) {
|
|
1484
|
+
var kb = Math.round(bytes / 1000);
|
|
1485
|
+
var mb = (bytes / 1e6).toFixed(1);
|
|
1486
|
+
if (kb < 990) {
|
|
1487
|
+
return kb + 'kB';
|
|
1488
|
+
}
|
|
1489
|
+
return mb + 'MB';
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
async function fetchSnapshotList() {
|
|
1493
|
+
await removeMissingSnapshots();
|
|
1494
|
+
var index = await fetchIndex();
|
|
1495
|
+
var snapshots = index.snapshots;
|
|
1496
|
+
snapshots = snapshots.filter(function(o) {return o.session == sessionId;});
|
|
1497
|
+
return snapshots.sort(function(a, b) {b.created > a.created;});
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
async function removeSnapshotById(id, gui) {
|
|
1501
|
+
await idb.del(id);
|
|
1502
|
+
return updateIndex(function(index) {
|
|
1503
|
+
index.snapshots = index.snapshots.filter(function(snap) {
|
|
1504
|
+
return snap.id != id;
|
|
1505
|
+
});
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
async function restoreSnapshotById(id, gui) {
|
|
1510
|
+
var buf = await idb.get(id);
|
|
1511
|
+
if (!buf) {
|
|
1512
|
+
console.log('Snapshot not available:', id);
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
var data = internal.unpackSession(buf);
|
|
1516
|
+
buf = null;
|
|
1517
|
+
gui.model.clear();
|
|
1518
|
+
importDatasets(data.datasets, gui);
|
|
1519
|
+
gui.clearMode();
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// Add datasets to the current project
|
|
1523
|
+
// TODO: figure out if interface data should be imported (e.g. should
|
|
1524
|
+
// visibility flag of imported layers be imported)
|
|
1525
|
+
function importSessionData(buf, gui) {
|
|
1526
|
+
if (buf instanceof ArrayBuffer) {
|
|
1527
|
+
buf = new Uint8Array(buf);
|
|
1528
|
+
}
|
|
1529
|
+
var data = internal.unpackSession(buf);
|
|
1530
|
+
importDatasets(data.datasets, gui);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
function importDatasets(datasets, gui) {
|
|
1534
|
+
gui.model.addDatasets(datasets);
|
|
1535
|
+
var target = findTargetLayer(datasets);
|
|
1536
|
+
gui.model.setDefaultTarget(target.layers, target.dataset);
|
|
1537
|
+
gui.model.updated({select: true, arc_count: true}); // arc_count to refresh display shapes
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
function captureSnapshot(gui) {
|
|
1541
|
+
var datasets = gui.model.getDatasets();
|
|
1542
|
+
var lyr = gui.model.getActiveLayer().layer;
|
|
1543
|
+
lyr.active = true;
|
|
1544
|
+
var obj = internal.exportDatasetsToPack(datasets);
|
|
1545
|
+
delete lyr.active;
|
|
1546
|
+
obj.gui = getGuiState(gui);
|
|
1547
|
+
return internal.pack(obj);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// TODO: capture gui state information to allow restoring more of the UI
|
|
1551
|
+
function getGuiState(gui) {
|
|
1552
|
+
return null;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
async function fetchIndex() {
|
|
1556
|
+
var index = await idb.get('msx_index');
|
|
1557
|
+
return index || {snapshots: []};
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
async function updateIndex(action) {
|
|
1561
|
+
return idb.update('msx_index', function(index) {
|
|
1562
|
+
if (!index || !Array.isArray(index.snapshots)) {
|
|
1563
|
+
index = {snapshots: []};
|
|
1564
|
+
}
|
|
1565
|
+
action(index);
|
|
1566
|
+
return index;
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
async function addToIndex(obj) {
|
|
1571
|
+
updateSessionData();
|
|
1572
|
+
return updateIndex(function(index) {
|
|
1573
|
+
index.snapshots.push(obj);
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
async function removeMissingSnapshots() {
|
|
1578
|
+
var keys = await idb.keys();
|
|
1579
|
+
return updateIndex(function(index) {
|
|
1580
|
+
index.snapshots = index.snapshots.filter(function(snap) {
|
|
1581
|
+
return keys.includes(snap.id);
|
|
1582
|
+
});
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
async function initialCleanup() {
|
|
1587
|
+
// (Safari workaround) remove any lingering data from past sessions
|
|
1588
|
+
if (getSessionData().length === 0) {
|
|
1589
|
+
await idb.clear();
|
|
1590
|
+
}
|
|
1591
|
+
// remove any snapshots that are not indexed
|
|
1592
|
+
var keys = await idb.keys();
|
|
1593
|
+
var indexedIds = (await fetchIndex()).snapshots.map(function(snap) {return snap.id;});
|
|
1594
|
+
keys.forEach(function(key) {
|
|
1595
|
+
if (isSnapshotId(key) && !indexedIds.includes(key)) {
|
|
1596
|
+
idb.del(key);
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1599
|
+
// remove old indexed snapshots
|
|
1600
|
+
return updateIndex(function(index) {
|
|
1601
|
+
index.snapshots = index.snapshots.filter(function(snap) {
|
|
1602
|
+
var msPerDay = 1000 * 60 * 60 * 24;
|
|
1603
|
+
var daysOld = (Date.now() - snap.created) / msPerDay;
|
|
1604
|
+
if (daysOld > 1) {
|
|
1605
|
+
if (keys.includes(snap.id)) idb.del(snap.id);
|
|
1606
|
+
return false;
|
|
1607
|
+
}
|
|
1608
|
+
return true;
|
|
1609
|
+
});
|
|
1610
|
+
return index;
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
async function getAvailableStorage() {
|
|
1615
|
+
var bytes;
|
|
1616
|
+
try {
|
|
1617
|
+
var estimate = await navigator.storage.estimate();
|
|
1618
|
+
bytes = (estimate.quota - estimate.usage);
|
|
1619
|
+
} catch(e) {
|
|
1620
|
+
return null;
|
|
1621
|
+
}
|
|
1622
|
+
var str = (bytes / 1e6).toFixed(1) + 'MB';
|
|
1623
|
+
if (str.length > 7) {
|
|
1624
|
+
str = (bytes / 1e9).toFixed(1) + 'GB';
|
|
1625
|
+
}
|
|
1626
|
+
if (str.length > 7) {
|
|
1627
|
+
str = (bytes / 1e12).toFixed(1) + 'TB';
|
|
1628
|
+
}
|
|
1629
|
+
if (parseFloat(str) >= 10) {
|
|
1630
|
+
str = str.replace(/\../, '');
|
|
1631
|
+
}
|
|
1632
|
+
return str;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
function findTargetLayer(datasets) {
|
|
1636
|
+
var target;
|
|
1637
|
+
datasets.forEach(function(dataset) {
|
|
1638
|
+
var lyr = dataset.layers.find(function(lyr) { return !!lyr.active; });
|
|
1639
|
+
if (lyr) target = {dataset: dataset, layers: [lyr]};
|
|
1640
|
+
});
|
|
1641
|
+
if (!target) {
|
|
1642
|
+
target = {dataset: datasets[0], layers: [datasets[0].layers[0]]};
|
|
1643
|
+
}
|
|
1644
|
+
return target;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// Clean up snapshot data (called just before browser tab is closed)
|
|
1648
|
+
async function finalCleanup() {
|
|
1649
|
+
// When called on 'beforeunload', idb.clear() seems to complete
|
|
1650
|
+
// before tab is unloaded in Chrome and Firefox, but not in Safari.
|
|
1651
|
+
// Calling idb.del(key) to selectively delete data for the current session
|
|
1652
|
+
// does not seem to complete in any browser.
|
|
1653
|
+
// So we wait until the last open session is ending at this URL, and delete
|
|
1654
|
+
// data for all recently open sessions.
|
|
1655
|
+
//
|
|
1656
|
+
var sessions = getSessionData().filter(function(item) {
|
|
1657
|
+
// remove current session
|
|
1658
|
+
var daysOld = (Date.now() - item.timestamp) / (1000 * 60 * 60 * 24);
|
|
1659
|
+
if (item.session == sessionId) return false;
|
|
1660
|
+
// also remove any lingering old sessions (ordinarily this shouldn't be needed)
|
|
1661
|
+
if (daysOld > 1) return false;
|
|
1662
|
+
return true;
|
|
1663
|
+
});
|
|
1664
|
+
setSessionData(sessions);
|
|
1665
|
+
if (sessions.length === 0) {
|
|
1666
|
+
await idb.clear();
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
function updateSessionData() {
|
|
1671
|
+
// make sure the current session is added to the list of open sessions
|
|
1672
|
+
var sessions = getSessionData();
|
|
1673
|
+
if (sessions.find(o => o.session == sessionId)) return;
|
|
1674
|
+
var entry = {
|
|
1675
|
+
session: sessionId,
|
|
1676
|
+
timestamp: Date.now()
|
|
1677
|
+
};
|
|
1678
|
+
setSessionData(sessions.concat([entry]));
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
function getSessionData() {
|
|
1682
|
+
try {
|
|
1683
|
+
var data = JSON.parse(window.localStorage.getItem('session_data'));
|
|
1684
|
+
return data || [];
|
|
1685
|
+
} catch(e) {}
|
|
1686
|
+
return [];
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
function setSessionData(arr) {
|
|
1690
|
+
window.localStorage.setItem('session_data', JSON.stringify(arr));
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1109
1693
|
// @cb function(<FileList>)
|
|
1110
1694
|
function DropControl(gui, el, cb) {
|
|
1111
1695
|
var area = El(el);
|
|
@@ -1391,18 +1975,24 @@
|
|
|
1391
1975
|
async function importFiles(fileData) {
|
|
1392
1976
|
var importOpts = readImportOpts();
|
|
1393
1977
|
var groups = groupFilesForImport(fileData, importOpts);
|
|
1978
|
+
var optStr = GUI.formatCommandOptions(importOpts);
|
|
1394
1979
|
fileData = null;
|
|
1395
1980
|
for (var group of groups) {
|
|
1396
1981
|
if (group.size > 4e7) {
|
|
1397
1982
|
gui.showProgressMessage('Importing');
|
|
1398
1983
|
await wait(35);
|
|
1399
1984
|
}
|
|
1400
|
-
|
|
1985
|
+
if (group[internal.PACKAGE_EXT]) {
|
|
1986
|
+
importSessionData(group[internal.PACKAGE_EXT].content, gui);
|
|
1987
|
+
} else {
|
|
1988
|
+
importDataset(group, importOpts);
|
|
1989
|
+
}
|
|
1990
|
+
importCount++;
|
|
1991
|
+
gui.session.fileImported(group.filename, optStr);
|
|
1401
1992
|
}
|
|
1402
1993
|
}
|
|
1403
1994
|
|
|
1404
1995
|
function importDataset(group, importOpts) {
|
|
1405
|
-
var optStr = GUI.formatCommandOptions(importOpts);
|
|
1406
1996
|
var dataset = internal.importContent(group, importOpts);
|
|
1407
1997
|
if (datasetIsEmpty(dataset)) return;
|
|
1408
1998
|
if (group.layername) {
|
|
@@ -1411,8 +2001,6 @@
|
|
|
1411
2001
|
// save import options for use by repair control, etc.
|
|
1412
2002
|
dataset.info.import_options = importOpts;
|
|
1413
2003
|
model.addDataset(dataset);
|
|
1414
|
-
importCount++;
|
|
1415
|
-
gui.session.fileImported(group.filename, optStr);
|
|
1416
2004
|
}
|
|
1417
2005
|
|
|
1418
2006
|
function addEmptyLayer() {
|
|
@@ -1756,235 +2344,6 @@
|
|
|
1756
2344
|
|
|
1757
2345
|
utils$1.inherit(Slider, EventDispatcher);
|
|
1758
2346
|
|
|
1759
|
-
function showPopupAlert(msg, title) {
|
|
1760
|
-
var self = {}, html = '';
|
|
1761
|
-
var _cancel, _close;
|
|
1762
|
-
var warningRxp = /^Warning: /;
|
|
1763
|
-
var el = El('div').appendTo('body').addClass('error-wrapper');
|
|
1764
|
-
var infoBox = El('div').appendTo(el).addClass('error-box info-box selectable');
|
|
1765
|
-
if (!title && warningRxp.test(msg)) {
|
|
1766
|
-
title = 'Warning';
|
|
1767
|
-
msg = msg.replace(warningRxp, '');
|
|
1768
|
-
}
|
|
1769
|
-
if (title) {
|
|
1770
|
-
html += `<div class="error-title">${title}</div>`;
|
|
1771
|
-
}
|
|
1772
|
-
html += `<p class="error-message">${msg}</p>`;
|
|
1773
|
-
El('div').appendTo(infoBox).addClass('close2-btn').on('click', function() {
|
|
1774
|
-
if (_cancel) _cancel();
|
|
1775
|
-
self.close();
|
|
1776
|
-
});
|
|
1777
|
-
El('div').appendTo(infoBox).addClass('error-content').html(html);
|
|
1778
|
-
|
|
1779
|
-
self.onCancel = function(cb) {
|
|
1780
|
-
_cancel = cb;
|
|
1781
|
-
return self;
|
|
1782
|
-
};
|
|
1783
|
-
|
|
1784
|
-
self.onClose = function(cb) {
|
|
1785
|
-
_close = cb;
|
|
1786
|
-
return self;
|
|
1787
|
-
};
|
|
1788
|
-
|
|
1789
|
-
self.button = function(label, cb) {
|
|
1790
|
-
El('div')
|
|
1791
|
-
.addClass("btn dialog-btn alert-btn")
|
|
1792
|
-
.appendTo(infoBox)
|
|
1793
|
-
.html(label)
|
|
1794
|
-
.on('click', function() {
|
|
1795
|
-
self.close();
|
|
1796
|
-
cb();
|
|
1797
|
-
});
|
|
1798
|
-
return self;
|
|
1799
|
-
};
|
|
1800
|
-
|
|
1801
|
-
self.close = function() {
|
|
1802
|
-
if (el) el.remove();
|
|
1803
|
-
if (_close) _close();
|
|
1804
|
-
el = _cancel = _close = null;
|
|
1805
|
-
};
|
|
1806
|
-
return self;
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
function AlertControl(gui) {
|
|
1810
|
-
var openAlert; // error popup
|
|
1811
|
-
var openPopup; // any popup
|
|
1812
|
-
|
|
1813
|
-
gui.addMode('alert', function() {}, closePopup);
|
|
1814
|
-
|
|
1815
|
-
gui.alert = function(str, title) {
|
|
1816
|
-
closePopup();
|
|
1817
|
-
openAlert = openPopup = showPopupAlert(str, title);
|
|
1818
|
-
// alert.button('close', gui.clearMode);
|
|
1819
|
-
openAlert.onClose(gui.clearMode);
|
|
1820
|
-
gui.enterMode('alert');
|
|
1821
|
-
};
|
|
1822
|
-
|
|
1823
|
-
gui.message = function(str, title) {
|
|
1824
|
-
if (openPopup) return; // don't stomp on another popup
|
|
1825
|
-
openPopup = showPopupAlert(str, title);
|
|
1826
|
-
openPopup.onClose(function() {openPopup = null;});
|
|
1827
|
-
};
|
|
1828
|
-
|
|
1829
|
-
function closePopup() {
|
|
1830
|
-
if (openPopup) openPopup.close();
|
|
1831
|
-
openPopup = openAlert = null;
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
function saveZipFile(zipfileName, files, done) {
|
|
1836
|
-
internal.zipAsync(files, function(err, buf) {
|
|
1837
|
-
if (err) {
|
|
1838
|
-
done(errorMessage(err));
|
|
1839
|
-
} else {
|
|
1840
|
-
saveBlobToLocalFile(zipfileName, new Blob([buf]), done);
|
|
1841
|
-
}
|
|
1842
|
-
});
|
|
1843
|
-
|
|
1844
|
-
function errorMessage(err) {
|
|
1845
|
-
var str = "Error creating Zip file";
|
|
1846
|
-
if (err.message) {
|
|
1847
|
-
str += ": " + err.message;
|
|
1848
|
-
}
|
|
1849
|
-
return str;
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
function saveFilesToServer(paths, data, done) {
|
|
1854
|
-
var i = -1;
|
|
1855
|
-
next();
|
|
1856
|
-
function next(err) {
|
|
1857
|
-
i++;
|
|
1858
|
-
if (err) return done(err);
|
|
1859
|
-
if (i >= data.length) return done();
|
|
1860
|
-
saveBlobToServer(paths[i], new Blob([data[i]]), next);
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
function saveBlobToServer(path, blob, done) {
|
|
1865
|
-
var q = '?file=' + encodeURIComponent(path);
|
|
1866
|
-
var url = window.location.origin + '/save' + q;
|
|
1867
|
-
window.fetch(url, {
|
|
1868
|
-
method: 'POST',
|
|
1869
|
-
credentials: 'include',
|
|
1870
|
-
body: blob
|
|
1871
|
-
}).then(function(resp) {
|
|
1872
|
-
if (resp.status == 400) {
|
|
1873
|
-
return resp.text();
|
|
1874
|
-
}
|
|
1875
|
-
}).then(function(err) {
|
|
1876
|
-
done(err);
|
|
1877
|
-
}).catch(function(resp) {
|
|
1878
|
-
done('connection to server was lost');
|
|
1879
|
-
});
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
async function saveBlobToLocalFile(filename, blob, done) {
|
|
1883
|
-
if (window.showSaveFilePicker) {
|
|
1884
|
-
saveBlobToSelectedFile(filename, blob, done);
|
|
1885
|
-
} else {
|
|
1886
|
-
saveBlobToDownloadsFolder(filename, blob, done);
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
|
-
function showSaveDialog(filename, blob, done) {
|
|
1891
|
-
showPopupAlert(`Save ${filename} to:`)
|
|
1892
|
-
.button('selected folder', function() {
|
|
1893
|
-
saveBlobToSelectedFile(filename, blob, done);
|
|
1894
|
-
})
|
|
1895
|
-
.button('downloads', function() {
|
|
1896
|
-
saveBlobToDownloadsFolder(filename, blob, done);
|
|
1897
|
-
})
|
|
1898
|
-
.onCancel(done);
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
async function saveBlobToSelectedFile(filename, blob, done) {
|
|
1902
|
-
// see: https://developer.chrome.com/articles/file-system-access/
|
|
1903
|
-
// note: saving multiple files to a directory using showDirectoryPicker()
|
|
1904
|
-
// does not work well (in Chrome). User is prompted for permission each time,
|
|
1905
|
-
// and some directories (like Downloads and Documents) are blocked.
|
|
1906
|
-
//
|
|
1907
|
-
var options = getSaveFileOptions(filename);
|
|
1908
|
-
var handle;
|
|
1909
|
-
try {
|
|
1910
|
-
handle = await window.showSaveFilePicker(options);
|
|
1911
|
-
var writable = await handle.createWritable();
|
|
1912
|
-
await writable.write(blob);
|
|
1913
|
-
await writable.close();
|
|
1914
|
-
} catch(e) {
|
|
1915
|
-
if (e.name == 'SecurityError') {
|
|
1916
|
-
// assuming this is a timeout error, with message like:
|
|
1917
|
-
// "Must be handling a user gesture to show a file picker."
|
|
1918
|
-
showSaveDialog(filename, blob, done);
|
|
1919
|
-
} else if (e.name == 'AbortError') {
|
|
1920
|
-
// fired if user clicks a cancel button (normal, no error message)
|
|
1921
|
-
// BUT: this kind of erro rmay also get fired when saving to a protected folder
|
|
1922
|
-
// (should show error message)
|
|
1923
|
-
done();
|
|
1924
|
-
} else {
|
|
1925
|
-
console.error(e.name, e.message, e);
|
|
1926
|
-
done('Save failed for an unknown reason');
|
|
1927
|
-
}
|
|
1928
|
-
return;
|
|
1929
|
-
}
|
|
1930
|
-
|
|
1931
|
-
done();
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
function getSaveFileOptions(filename) {
|
|
1935
|
-
// see: https://wicg.github.io/file-system-access/#api-filepickeroptions
|
|
1936
|
-
var type = internal.guessInputFileType(filename);
|
|
1937
|
-
var ext = internal.getFileExtension(filename).toLowerCase();
|
|
1938
|
-
var accept = {};
|
|
1939
|
-
if (ext == 'kml') {
|
|
1940
|
-
accept['application/vnd.google-earth.kml+xml'] = ['.kml'];
|
|
1941
|
-
} else if (ext == 'svg') {
|
|
1942
|
-
accept['image/svg+xml'] = ['.svg'];
|
|
1943
|
-
} else if (ext == 'zip') {
|
|
1944
|
-
accept['application/zip'] == ['.zip'];
|
|
1945
|
-
} else if (type == 'text') {
|
|
1946
|
-
accept['text/csv'] = ['.csv', '.tsv', '.tab', '.txt'];
|
|
1947
|
-
} else if (type == 'json') {
|
|
1948
|
-
accept['application/json'] = ['.json', '.geojson', '.topojson'];
|
|
1949
|
-
} else {
|
|
1950
|
-
accept['application/octet-stream'] = ['.' + ext];
|
|
1951
|
-
}
|
|
1952
|
-
return {
|
|
1953
|
-
suggestedName: filename,
|
|
1954
|
-
// If startIn is given, Chrome will always start there
|
|
1955
|
-
// Default is to start in the previously selected dir (better)
|
|
1956
|
-
// // startIn: 'downloads', // or: desktop, documents, [file handle], [directory handle]
|
|
1957
|
-
types: [{
|
|
1958
|
-
description: 'Files',
|
|
1959
|
-
accept: accept
|
|
1960
|
-
}]
|
|
1961
|
-
};
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
function saveBlobToDownloadsFolder(filename, blob, done) {
|
|
1966
|
-
var anchor, blobUrl;
|
|
1967
|
-
try {
|
|
1968
|
-
blobUrl = URL.createObjectURL(blob);
|
|
1969
|
-
} catch(e) {
|
|
1970
|
-
done("Mapshaper can't export files from this browser. Try switching to Chrome or Firefox.");
|
|
1971
|
-
return;
|
|
1972
|
-
}
|
|
1973
|
-
anchor = El('a').attr('href', '#').appendTo('body').node();
|
|
1974
|
-
anchor.href = blobUrl;
|
|
1975
|
-
anchor.download = filename;
|
|
1976
|
-
var clickEvent = document.createEvent("MouseEvent");
|
|
1977
|
-
clickEvent.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false,
|
|
1978
|
-
false, false, false, 0, null);
|
|
1979
|
-
anchor.dispatchEvent(clickEvent);
|
|
1980
|
-
setTimeout(function() {
|
|
1981
|
-
// Revoke blob url to release memory; timeout needed in firefox
|
|
1982
|
-
URL.revokeObjectURL(blobUrl);
|
|
1983
|
-
anchor.parentNode.removeChild(anchor);
|
|
1984
|
-
done();
|
|
1985
|
-
}, 400);
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
2347
|
// replace default error, stop and message functions
|
|
1989
2348
|
function setLoggingForGUI(gui) {
|
|
1990
2349
|
function stop() {
|
|
@@ -2488,7 +2847,7 @@
|
|
|
2488
2847
|
return copy;
|
|
2489
2848
|
}
|
|
2490
2849
|
|
|
2491
|
-
var wgs84 = internal.
|
|
2850
|
+
var wgs84 = internal.parseCrsString('wgs84');
|
|
2492
2851
|
var toWGS84 = internal.getProjTransform2(src, wgs84);
|
|
2493
2852
|
var fromWGS84 = internal.getProjTransform2(wgs84, dest);
|
|
2494
2853
|
|
|
@@ -2597,7 +2956,7 @@
|
|
|
2597
2956
|
function setDisplayProjection(gui, cmd) {
|
|
2598
2957
|
var arg = cmd.replace(/^projd[ ]*/, '');
|
|
2599
2958
|
if (arg) {
|
|
2600
|
-
gui.map.setDisplayCRS(internal.
|
|
2959
|
+
gui.map.setDisplayCRS(internal.parseCrsString(arg));
|
|
2601
2960
|
} else {
|
|
2602
2961
|
gui.map.setDisplayCRS(null);
|
|
2603
2962
|
}
|
|
@@ -4320,7 +4679,8 @@
|
|
|
4320
4679
|
|
|
4321
4680
|
// Only render edit mode button/menu if this option is present
|
|
4322
4681
|
if (gui.options.inspectorControl) {
|
|
4323
|
-
|
|
4682
|
+
// use z-index so the menu is over other buttons
|
|
4683
|
+
btn = gui.buttons.addButton('#pointer-icon').addClass('menu-btn pointer-btn'),
|
|
4324
4684
|
menu = El('div').addClass('nav-sub-menu').appendTo(btn.node());
|
|
4325
4685
|
|
|
4326
4686
|
btn.on('mouseleave', function() {
|
|
@@ -5973,7 +6333,7 @@
|
|
|
5973
6333
|
return hits;
|
|
5974
6334
|
}
|
|
5975
6335
|
|
|
5976
|
-
|
|
6336
|
+
function vertexTest(x, y) {
|
|
5977
6337
|
var maxDist = getZoomAdjustedHitBuffer(25, 2),
|
|
5978
6338
|
cands = findHitCandidates(x, y, maxDist);
|
|
5979
6339
|
sortByDistance(x, y, cands, displayLayer.arcs);
|
|
@@ -10309,7 +10669,7 @@
|
|
|
10309
10669
|
}
|
|
10310
10670
|
|
|
10311
10671
|
if (!internal.isWebMercator(crs)) {
|
|
10312
|
-
gui.map.setDisplayCRS(internal.
|
|
10672
|
+
gui.map.setDisplayCRS(internal.parseCrsString('webmercator'));
|
|
10313
10673
|
}
|
|
10314
10674
|
var bbox = getLonLatBounds();
|
|
10315
10675
|
if (!checkBounds(bbox)) {
|
|
@@ -10427,7 +10787,7 @@
|
|
|
10427
10787
|
this.setDisplayCRS = function(crs) {
|
|
10428
10788
|
// TODO: update bounds of frame layer, if there is a frame layer
|
|
10429
10789
|
var oldCRS = this.getDisplayCRS();
|
|
10430
|
-
var newCRS = utils$1.isString(crs) ? internal.
|
|
10790
|
+
var newCRS = utils$1.isString(crs) ? internal.parseCrsString(crs) : crs;
|
|
10431
10791
|
// TODO: handle case that old and new CRS are the same
|
|
10432
10792
|
_dynamicCRS = newCRS;
|
|
10433
10793
|
if (!_activeLyr) return; // stop here if no layers have been selected
|
|
@@ -10484,12 +10844,12 @@
|
|
|
10484
10844
|
_activeLyr.style = getActiveStyle(_activeLyr.layer, gui.state.dark_basemap);
|
|
10485
10845
|
_activeLyr.active = true;
|
|
10486
10846
|
|
|
10487
|
-
if (e.flags.same_table) {
|
|
10847
|
+
if (e.flags.same_table && !e.flags.proj) {
|
|
10488
10848
|
// data may have changed; if popup is open, it needs to be refreshed
|
|
10489
10849
|
gui.dispatchEvent('popup-needs-refresh');
|
|
10490
10850
|
} else if (_hit) {
|
|
10491
|
-
_hit.setLayer(_activeLyr);
|
|
10492
10851
|
_hit.clearSelection();
|
|
10852
|
+
_hit.setLayer(_activeLyr);
|
|
10493
10853
|
}
|
|
10494
10854
|
|
|
10495
10855
|
updateVisibleMapLayers();
|
|
@@ -10818,6 +11178,7 @@
|
|
|
10818
11178
|
homeControl: true,
|
|
10819
11179
|
zoomControl: true,
|
|
10820
11180
|
inspectorControl: true,
|
|
11181
|
+
saveControl: true,
|
|
10821
11182
|
disableNavigation: false,
|
|
10822
11183
|
showMouseCoordinates: true,
|
|
10823
11184
|
focus: true
|
|
@@ -10830,6 +11191,7 @@
|
|
|
10830
11191
|
gui.buttons = new SidebarButtons(gui);
|
|
10831
11192
|
gui.map = new MshpMap(gui);
|
|
10832
11193
|
gui.interaction = new InteractionMode(gui);
|
|
11194
|
+
gui.sessionSave = new SessionSnapshots(gui);
|
|
10833
11195
|
gui.session = new SessionHistory(gui);
|
|
10834
11196
|
gui.undo = new Undo(gui);
|
|
10835
11197
|
gui.state = {};
|