mapshaper 0.7.3 → 0.7.5
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/bin/mapshaper-gui +31 -7
- package/mapshaper.js +12336 -11482
- package/package.json +5 -6
- package/www/geopackage.js +10 -10
- package/www/index.html +33 -4
- package/www/mapshaper-gui.js +1639 -1249
- package/www/mapshaper.js +12336 -11482
- package/www/modules.js +5 -5
- package/www/page.css +161 -0
package/www/mapshaper-gui.js
CHANGED
|
@@ -1,108 +1,5 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
|
|
3
|
-
var utils = /*#__PURE__*/Object.freeze({
|
|
4
|
-
__proto__: null,
|
|
5
|
-
get addThousandsSep () { return addThousandsSep; },
|
|
6
|
-
get addslashes () { return addslashes; },
|
|
7
|
-
get arrayToIndex () { return arrayToIndex; },
|
|
8
|
-
get clamp () { return clamp; },
|
|
9
|
-
get cleanNumericString () { return cleanNumericString; },
|
|
10
|
-
get contains () { return contains; },
|
|
11
|
-
get copyElements () { return copyElements; },
|
|
12
|
-
get countValues () { return countValues; },
|
|
13
|
-
get createBuffer () { return createBuffer; },
|
|
14
|
-
get default () { return utils; },
|
|
15
|
-
get defaults () { return defaults; },
|
|
16
|
-
get difference () { return difference; },
|
|
17
|
-
get endsWith () { return endsWith; },
|
|
18
|
-
get every () { return every; },
|
|
19
|
-
get expandoBuffer () { return expandoBuffer; },
|
|
20
|
-
get extend () { return extend; },
|
|
21
|
-
get extendBuffer () { return extendBuffer; },
|
|
22
|
-
get find () { return find; },
|
|
23
|
-
get findMedian () { return findMedian; },
|
|
24
|
-
get findQuantile () { return findQuantile; },
|
|
25
|
-
get findRankByValue () { return findRankByValue; },
|
|
26
|
-
get findStringPrefix () { return findStringPrefix; },
|
|
27
|
-
get findValueByPct () { return findValueByPct; },
|
|
28
|
-
get findValueByRank () { return findValueByRank; },
|
|
29
|
-
get forEach () { return forEach; },
|
|
30
|
-
get forEachProperty () { return forEachProperty; },
|
|
31
|
-
get format () { return format; },
|
|
32
|
-
get formatDateISO () { return formatDateISO; },
|
|
33
|
-
get formatIntlNumber () { return formatIntlNumber; },
|
|
34
|
-
get formatNumber () { return formatNumber; },
|
|
35
|
-
get formatNumberForDisplay () { return formatNumberForDisplay; },
|
|
36
|
-
get formatVersionedName () { return formatVersionedName; },
|
|
37
|
-
get formatter () { return formatter; },
|
|
38
|
-
get genericSort () { return genericSort; },
|
|
39
|
-
get getArrayBounds () { return getArrayBounds; },
|
|
40
|
-
get getGenericComparator () { return getGenericComparator; },
|
|
41
|
-
get getKeyComparator () { return getKeyComparator; },
|
|
42
|
-
get getSortedIds () { return getSortedIds; },
|
|
43
|
-
get getUniqueName () { return getUniqueName; },
|
|
44
|
-
get groupBy () { return groupBy; },
|
|
45
|
-
get htmlEscape () { return htmlEscape; },
|
|
46
|
-
get indexOf () { return indexOf; },
|
|
47
|
-
get indexOn () { return indexOn; },
|
|
48
|
-
get inherit () { return inherit; },
|
|
49
|
-
get initializeArray () { return initializeArray; },
|
|
50
|
-
get intersection () { return intersection; },
|
|
51
|
-
get isArray () { return isArray; },
|
|
52
|
-
get isArrayLike () { return isArrayLike; },
|
|
53
|
-
get isBoolean () { return isBoolean; },
|
|
54
|
-
get isDate () { return isDate; },
|
|
55
|
-
get isEven () { return isEven; },
|
|
56
|
-
get isFiniteNumber () { return isFiniteNumber; },
|
|
57
|
-
get isFunction () { return isFunction; },
|
|
58
|
-
get isInteger () { return isInteger; },
|
|
59
|
-
get isNonNegNumber () { return isNonNegNumber; },
|
|
60
|
-
get isNumber () { return isNumber; },
|
|
61
|
-
get isObject () { return isObject; },
|
|
62
|
-
get isOdd () { return isOdd; },
|
|
63
|
-
get isPromise () { return isPromise; },
|
|
64
|
-
get isString () { return isString; },
|
|
65
|
-
get isValidNumber () { return isValidNumber; },
|
|
66
|
-
get lpad () { return lpad; },
|
|
67
|
-
get ltrim () { return ltrim; },
|
|
68
|
-
get mean () { return mean; },
|
|
69
|
-
get merge () { return merge; },
|
|
70
|
-
get mergeNames () { return mergeNames; },
|
|
71
|
-
get numToStr () { return numToStr; },
|
|
72
|
-
get parseIntlNumber () { return parseIntlNumber; },
|
|
73
|
-
get parseNumber () { return parseNumber; },
|
|
74
|
-
get parsePercent () { return parsePercent; },
|
|
75
|
-
get parseString () { return parseString; },
|
|
76
|
-
get pickOne () { return pickOne; },
|
|
77
|
-
get pluck () { return pluck; },
|
|
78
|
-
get pluralSuffix () { return pluralSuffix; },
|
|
79
|
-
get promisify () { return promisify; },
|
|
80
|
-
get quicksort () { return quicksort; },
|
|
81
|
-
get quicksortPartition () { return quicksortPartition; },
|
|
82
|
-
get range () { return range; },
|
|
83
|
-
get reduceAsync () { return reduceAsync; },
|
|
84
|
-
get regexEscape () { return regexEscape; },
|
|
85
|
-
get reorderArray () { return reorderArray; },
|
|
86
|
-
get repeat () { return repeat; },
|
|
87
|
-
get repeatString () { return repeatString; },
|
|
88
|
-
get replaceArray () { return replaceArray; },
|
|
89
|
-
get rpad () { return rpad; },
|
|
90
|
-
get rtrim () { return rtrim; },
|
|
91
|
-
get shuffle () { return shuffle; },
|
|
92
|
-
get some () { return some; },
|
|
93
|
-
get sortArrayIndex () { return sortArrayIndex; },
|
|
94
|
-
get sortOn () { return sortOn; },
|
|
95
|
-
get splitLines () { return splitLines; },
|
|
96
|
-
get sum () { return sum; },
|
|
97
|
-
get toArray () { return toArray; },
|
|
98
|
-
get toBuffer () { return toBuffer; },
|
|
99
|
-
get trim () { return trim; },
|
|
100
|
-
get trimQuotes () { return trimQuotes; },
|
|
101
|
-
get uniq () { return uniq; },
|
|
102
|
-
get uniqifyNames () { return uniqifyNames; },
|
|
103
|
-
get wildcardToRegExp () { return wildcardToRegExp; }
|
|
104
|
-
});
|
|
105
|
-
|
|
106
3
|
var api = window.mapshaper; // assuming mapshaper is in global scope
|
|
107
4
|
var mapshaper = api,
|
|
108
5
|
utils$1 = api.utils,
|
|
@@ -113,7 +10,7 @@
|
|
|
113
10
|
UserError$1 = internal.UserError,
|
|
114
11
|
message$1 = internal.message, // stop, error and message are overridden in gui-proxy.js
|
|
115
12
|
stop$1 = internal.stop,
|
|
116
|
-
error$
|
|
13
|
+
error$2 = internal.error;
|
|
117
14
|
|
|
118
15
|
api.enableLogging();
|
|
119
16
|
|
|
@@ -232,7 +129,7 @@
|
|
|
232
129
|
evt = new EventData(this.type);
|
|
233
130
|
evt.target = this.target;
|
|
234
131
|
} else if (evt.target != this.target || evt.type != this.type) {
|
|
235
|
-
error$
|
|
132
|
+
error$2("[Handler] event target/type have changed.");
|
|
236
133
|
}
|
|
237
134
|
this.callback.call(this.listener, evt);
|
|
238
135
|
};
|
|
@@ -481,10 +378,10 @@
|
|
|
481
378
|
try {
|
|
482
379
|
els = root.querySelectorAll(selector);
|
|
483
380
|
} catch (e) {
|
|
484
|
-
error$
|
|
381
|
+
error$2("Invalid selector:", selector);
|
|
485
382
|
}
|
|
486
383
|
} else {
|
|
487
|
-
error$
|
|
384
|
+
error$2("This browser doesn't support CSS query selectors");
|
|
488
385
|
}
|
|
489
386
|
return utils$1.toArray(els);
|
|
490
387
|
};
|
|
@@ -522,7 +419,7 @@
|
|
|
522
419
|
};
|
|
523
420
|
|
|
524
421
|
function El(ref) {
|
|
525
|
-
if (!ref) error$
|
|
422
|
+
if (!ref) error$2("Element() needs a reference");
|
|
526
423
|
if (ref instanceof El) {
|
|
527
424
|
return ref;
|
|
528
425
|
}
|
|
@@ -543,7 +440,7 @@
|
|
|
543
440
|
} else if (ref.tagName) {
|
|
544
441
|
node = ref;
|
|
545
442
|
}
|
|
546
|
-
if (!node) error$
|
|
443
|
+
if (!node) error$2("Unmatched element selector:", ref);
|
|
547
444
|
this.el = node;
|
|
548
445
|
}
|
|
549
446
|
|
|
@@ -1087,7 +984,7 @@
|
|
|
1087
984
|
}
|
|
1088
985
|
var val = utils$1.clamp(arg, _min, _max);
|
|
1089
986
|
if (!_validator(val)) {
|
|
1090
|
-
error$
|
|
987
|
+
error$2("ClickText#value() invalid value:", arg);
|
|
1091
988
|
} else {
|
|
1092
989
|
_value = val;
|
|
1093
990
|
}
|
|
@@ -1418,6 +1315,7 @@
|
|
|
1418
1315
|
//
|
|
1419
1316
|
var options = getSaveFileOptions(filename);
|
|
1420
1317
|
var handle;
|
|
1318
|
+
done = done || function() {};
|
|
1421
1319
|
try {
|
|
1422
1320
|
handle = await window.showSaveFilePicker(options);
|
|
1423
1321
|
var writable = await handle.createWritable();
|
|
@@ -1519,13 +1417,36 @@
|
|
|
1519
1417
|
// https://github.com/jakearchibald/idb-keyval
|
|
1520
1418
|
var sessionId = getUniqId('session');
|
|
1521
1419
|
var snapshotCount = 0;
|
|
1420
|
+
// IDs of snapshots created (and not removed) by this tab. Tracked in memory
|
|
1421
|
+
// so the pagehide handler can fire a single batched delMany() without first
|
|
1422
|
+
// awaiting idb.keys() -- the page may not survive the round trip.
|
|
1423
|
+
var ownSnapshotIds = new Set();
|
|
1424
|
+
|
|
1425
|
+
// Lifecycle constants for snapshot cleanup.
|
|
1426
|
+
// HEARTBEAT_INTERVAL_MS: how often this tab refreshes its localStorage entry.
|
|
1427
|
+
// STALE_THRESHOLD_MS: a session whose heartbeat is older than this is treated
|
|
1428
|
+
// as dead. Generous enough to tolerate backgrounded/throttled tabs.
|
|
1429
|
+
// BROADCAST_DISCOVERY_MS: how long startup waits for live tabs to identify
|
|
1430
|
+
// themselves over BroadcastChannel before deciding what to delete.
|
|
1431
|
+
var HEARTBEAT_INTERVAL_MS = 30 * 1000;
|
|
1432
|
+
var STALE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
1433
|
+
var BROADCAST_DISCOVERY_MS = 200;
|
|
1434
|
+
var SESSION_DATA_KEY = 'session_data';
|
|
1435
|
+
var BROADCAST_CHANNEL_NAME = 'mapshaper-snapshots';
|
|
1522
1436
|
|
|
1523
1437
|
function getUniqId(prefix) {
|
|
1524
1438
|
return prefix + '_' + (Math.random() + 1).toString(36).substring(2,8);
|
|
1525
1439
|
}
|
|
1526
1440
|
|
|
1441
|
+
function getSessionFromSnapshotId(snapshotId) {
|
|
1442
|
+
// Snapshot ids look like 'session_<6chars>_<NNN>'. The session id is the
|
|
1443
|
+
// 'session_<6chars>' prefix.
|
|
1444
|
+
var m = /^(session_[a-z0-9]+)_\d+$/.exec(snapshotId);
|
|
1445
|
+
return m ? m[1] : null;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1527
1448
|
function isSnapshotId(str) {
|
|
1528
|
-
return
|
|
1449
|
+
return getSessionFromSnapshotId(str) !== null;
|
|
1529
1450
|
}
|
|
1530
1451
|
|
|
1531
1452
|
function SessionSnapshots(gui) {
|
|
@@ -1543,13 +1464,20 @@
|
|
|
1543
1464
|
return;
|
|
1544
1465
|
}
|
|
1545
1466
|
menu = El('div').addClass('nav-sub-menu save-menu').appendTo(btn.node());
|
|
1467
|
+
startLifecycle();
|
|
1546
1468
|
await initialCleanup();
|
|
1547
1469
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1470
|
+
// 'pagehide' is more reliable than 'unload' across modern browsers
|
|
1471
|
+
// (Chrome's bfcache rules increasingly suppress 'unload'). Sync work
|
|
1472
|
+
// (localStorage write, BroadcastChannel notice) always completes; the
|
|
1473
|
+
// best-effort delMany() below frequently completes in Chrome/Firefox and
|
|
1474
|
+
// sometimes in Safari, but is not relied upon -- the next session's
|
|
1475
|
+
// startup cleanup is the safety net.
|
|
1476
|
+
window.addEventListener('pagehide', function(e) {
|
|
1477
|
+
if (e.persisted) return; // bfcache: tab may come back, leave entry alive
|
|
1478
|
+
removeOwnSession();
|
|
1479
|
+
announceLeaving();
|
|
1480
|
+
attemptOwnDataDeletion();
|
|
1553
1481
|
});
|
|
1554
1482
|
|
|
1555
1483
|
btn.on('mouseenter', function() {
|
|
@@ -1573,12 +1501,6 @@
|
|
|
1573
1501
|
var snapshots = await fetchSnapshotList();
|
|
1574
1502
|
|
|
1575
1503
|
menu.empty();
|
|
1576
|
-
addMenuLink({
|
|
1577
|
-
slug: 'stash',
|
|
1578
|
-
// label: 'save data snapshot',
|
|
1579
|
-
label: 'create a snapshot',
|
|
1580
|
-
action: saveSnapshot
|
|
1581
|
-
});
|
|
1582
1504
|
|
|
1583
1505
|
if (!gui.session.isEmpty()) {
|
|
1584
1506
|
// Surface the console "history" command via the snapshot menu so users
|
|
@@ -1593,6 +1515,15 @@
|
|
|
1593
1515
|
});
|
|
1594
1516
|
}
|
|
1595
1517
|
|
|
1518
|
+
addMenuLink({
|
|
1519
|
+
slug: 'stash',
|
|
1520
|
+
// label: 'save data snapshot',
|
|
1521
|
+
label: 'create a snapshot',
|
|
1522
|
+
action: saveSnapshot
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
|
|
1596
1527
|
// var available = await getAvailableStorage();
|
|
1597
1528
|
// if (available) {
|
|
1598
1529
|
// El('div').addClass('save-menu-entry').text(available + ' available').appendTo(menu);
|
|
@@ -1676,6 +1607,7 @@
|
|
|
1676
1607
|
};
|
|
1677
1608
|
|
|
1678
1609
|
await idb.set(entry.id, obj);
|
|
1610
|
+
ownSnapshotIds.add(entry.id);
|
|
1679
1611
|
await addToIndex(entry);
|
|
1680
1612
|
renderMenu();
|
|
1681
1613
|
}
|
|
@@ -1690,7 +1622,7 @@
|
|
|
1690
1622
|
}
|
|
1691
1623
|
|
|
1692
1624
|
async function fetchSnapshotList() {
|
|
1693
|
-
await
|
|
1625
|
+
await pruneIndexAgainstKeys();
|
|
1694
1626
|
var index = await fetchIndex();
|
|
1695
1627
|
var snapshots = index.snapshots;
|
|
1696
1628
|
snapshots = snapshots.filter(function(o) {return o.session == sessionId;});
|
|
@@ -1699,6 +1631,7 @@
|
|
|
1699
1631
|
|
|
1700
1632
|
async function removeSnapshotById(id, gui) {
|
|
1701
1633
|
await idb.del(id);
|
|
1634
|
+
ownSnapshotIds.delete(id);
|
|
1702
1635
|
return updateIndex(function(index) {
|
|
1703
1636
|
index.snapshots = index.snapshots.filter(function(snap) {
|
|
1704
1637
|
return snap.id != id;
|
|
@@ -1796,13 +1729,16 @@
|
|
|
1796
1729
|
}
|
|
1797
1730
|
|
|
1798
1731
|
async function addToIndex(obj) {
|
|
1799
|
-
|
|
1732
|
+
touchOwnSession();
|
|
1800
1733
|
return updateIndex(function(index) {
|
|
1801
1734
|
index.snapshots.push(obj);
|
|
1802
1735
|
});
|
|
1803
1736
|
}
|
|
1804
1737
|
|
|
1805
|
-
|
|
1738
|
+
// Drop index entries whose underlying IndexedDB blob has gone missing
|
|
1739
|
+
// (e.g. cleared by another tab). Cheaper than reclaimDeadSessionData; used
|
|
1740
|
+
// before rendering the menu to keep stale entries out of the UI.
|
|
1741
|
+
async function pruneIndexAgainstKeys() {
|
|
1806
1742
|
var keys = await idb.keys();
|
|
1807
1743
|
return updateIndex(function(index) {
|
|
1808
1744
|
index.snapshots = index.snapshots.filter(function(snap) {
|
|
@@ -1811,32 +1747,65 @@
|
|
|
1811
1747
|
});
|
|
1812
1748
|
}
|
|
1813
1749
|
|
|
1750
|
+
// Run on every fresh page load. Aggressively reclaim space by deleting any
|
|
1751
|
+
// snapshot whose owning session is no longer alive.
|
|
1814
1752
|
async function initialCleanup() {
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1753
|
+
touchOwnSession();
|
|
1754
|
+
pruneStaleSessionData();
|
|
1755
|
+
var liveSessions = await discoverLiveSessions();
|
|
1756
|
+
await reclaimDeadSessionData(liveSessions);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// Delete every snapshot in IndexedDB whose session id is not in liveSessions,
|
|
1760
|
+
// and keep the on-disk index consistent with the actual key set.
|
|
1761
|
+
async function reclaimDeadSessionData(liveSessions) {
|
|
1820
1762
|
var keys = await idb.keys();
|
|
1821
|
-
var
|
|
1763
|
+
var doomedKeys = [];
|
|
1764
|
+
var doomedSessions = new Set();
|
|
1822
1765
|
keys.forEach(function(key) {
|
|
1823
|
-
|
|
1824
|
-
|
|
1766
|
+
var sid = getSessionFromSnapshotId(key);
|
|
1767
|
+
if (sid && !liveSessions.has(sid)) {
|
|
1768
|
+
doomedKeys.push(key);
|
|
1769
|
+
doomedSessions.add(sid);
|
|
1825
1770
|
}
|
|
1826
1771
|
});
|
|
1827
|
-
|
|
1772
|
+
|
|
1773
|
+
// Sum sizes from the index for an informative log message. We only know
|
|
1774
|
+
// the size of snapshots that still have an index entry; orphaned blobs
|
|
1775
|
+
// are counted but their sizes contribute 0.
|
|
1776
|
+
var sizeBytes = 0;
|
|
1777
|
+
if (doomedKeys.length) {
|
|
1778
|
+
var index = await fetchIndex();
|
|
1779
|
+
var doomedKeySet = new Set(doomedKeys);
|
|
1780
|
+
index.snapshots.forEach(function(snap) {
|
|
1781
|
+
if (doomedKeySet.has(snap.id) && typeof snap.size === 'number') {
|
|
1782
|
+
sizeBytes += snap.size;
|
|
1783
|
+
}
|
|
1784
|
+
});
|
|
1785
|
+
await Promise.all(doomedKeys.map(function(k) { return idb.del(k); }));
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// Drop index entries pointing to deleted snapshots, and any entries whose
|
|
1789
|
+
// session is dead even if the underlying key was already gone.
|
|
1790
|
+
var remainingKeys = await idb.keys();
|
|
1791
|
+
var keySet = new Set(remainingKeys);
|
|
1828
1792
|
await updateIndex(function(index) {
|
|
1829
1793
|
index.snapshots = index.snapshots.filter(function(snap) {
|
|
1830
|
-
|
|
1831
|
-
var
|
|
1832
|
-
|
|
1833
|
-
if (keys.includes(snap.id)) idb.del(snap.id);
|
|
1834
|
-
return false;
|
|
1835
|
-
}
|
|
1836
|
-
return true;
|
|
1794
|
+
if (!keySet.has(snap.id)) return false;
|
|
1795
|
+
var sid = getSessionFromSnapshotId(snap.id);
|
|
1796
|
+
return sid && liveSessions.has(sid);
|
|
1837
1797
|
});
|
|
1838
|
-
return index;
|
|
1839
1798
|
});
|
|
1799
|
+
|
|
1800
|
+
if (doomedKeys.length) {
|
|
1801
|
+
var msg = '[mapshaper] startup cleanup reclaimed ' +
|
|
1802
|
+
doomedKeys.length + ' snapshot' + (doomedKeys.length === 1 ? '' : 's') +
|
|
1803
|
+
' from ' + doomedSessions.size + ' stale session' +
|
|
1804
|
+
(doomedSessions.size === 1 ? '' : 's');
|
|
1805
|
+
var sizeStr = sizeBytes > 0 ? formatSize(sizeBytes) : '';
|
|
1806
|
+
if (sizeStr) msg += ' (' + sizeStr + ')';
|
|
1807
|
+
console.log(msg);
|
|
1808
|
+
}
|
|
1840
1809
|
}
|
|
1841
1810
|
|
|
1842
1811
|
async function getAvailableStorage() {
|
|
@@ -1874,52 +1843,164 @@
|
|
|
1874
1843
|
return target;
|
|
1875
1844
|
}
|
|
1876
1845
|
|
|
1877
|
-
//
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1846
|
+
// Heartbeat + BroadcastChannel state. The localStorage map and the channel
|
|
1847
|
+
// together let other tabs identify themselves on demand and let crashed tabs'
|
|
1848
|
+
// data be reclaimed safely.
|
|
1849
|
+
//
|
|
1850
|
+
// localStorage 'session_data' shape: { <sessionId>: <lastSeenMs>, ... }
|
|
1851
|
+
// (The previous build stored an array; old data is overwritten on first
|
|
1852
|
+
// touchOwnSession() call. Only used for cleanup heuristics, not user data.)
|
|
1853
|
+
|
|
1854
|
+
var _heartbeatTimer = null;
|
|
1855
|
+
var _channel = null;
|
|
1856
|
+
|
|
1857
|
+
function startLifecycle() {
|
|
1858
|
+
touchOwnSession();
|
|
1859
|
+
_heartbeatTimer = setInterval(touchOwnSession, HEARTBEAT_INTERVAL_MS);
|
|
1860
|
+
if (typeof BroadcastChannel == 'function') {
|
|
1861
|
+
try {
|
|
1862
|
+
_channel = new BroadcastChannel(BROADCAST_CHANNEL_NAME);
|
|
1863
|
+
_channel.onmessage = function(e) {
|
|
1864
|
+
var msg = e.data;
|
|
1865
|
+
if (!msg || msg.from === sessionId) return;
|
|
1866
|
+
if (msg.type === 'whois') {
|
|
1867
|
+
try {
|
|
1868
|
+
_channel.postMessage({type: 'iam', from: sessionId});
|
|
1869
|
+
} catch (err) {} // channel can throw if tab is being torn down
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1872
|
+
} catch (err) {
|
|
1873
|
+
_channel = null; // sandboxed contexts may forbid BroadcastChannel
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
// Discover other live tabs. Returns a Set of session ids that should be
|
|
1879
|
+
// considered alive (always includes our own).
|
|
1880
|
+
function discoverLiveSessions() {
|
|
1881
|
+
return new Promise(function(resolve) {
|
|
1882
|
+
var live = new Set([sessionId]);
|
|
1883
|
+
// localStorage heartbeat data is the durable signal; it survives
|
|
1884
|
+
// backgrounded/throttled tabs that may not respond to BroadcastChannel
|
|
1885
|
+
// promptly.
|
|
1886
|
+
var data = readSessionData();
|
|
1887
|
+
Object.keys(data).forEach(function(sid) {
|
|
1888
|
+
if (Date.now() - data[sid] < STALE_THRESHOLD_MS) {
|
|
1889
|
+
live.add(sid);
|
|
1890
|
+
}
|
|
1891
|
+
});
|
|
1892
|
+
if (!_channel) {
|
|
1893
|
+
resolve(live);
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
// Broadcast 'whois' and add any tab that responds within the discovery
|
|
1897
|
+
// window. This is fast and authoritative for foreground tabs.
|
|
1898
|
+
var listener = function(e) {
|
|
1899
|
+
var msg = e.data;
|
|
1900
|
+
if (msg && msg.type === 'iam' && msg.from && msg.from !== sessionId) {
|
|
1901
|
+
live.add(msg.from);
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
_channel.addEventListener('message', listener);
|
|
1905
|
+
try {
|
|
1906
|
+
_channel.postMessage({type: 'whois', from: sessionId});
|
|
1907
|
+
} catch (err) {}
|
|
1908
|
+
setTimeout(function() {
|
|
1909
|
+
_channel.removeEventListener('message', listener);
|
|
1910
|
+
resolve(live);
|
|
1911
|
+
}, BROADCAST_DISCOVERY_MS);
|
|
1893
1912
|
});
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
function announceLeaving() {
|
|
1916
|
+
if (!_channel) return;
|
|
1917
|
+
try {
|
|
1918
|
+
_channel.postMessage({type: 'leaving', from: sessionId});
|
|
1919
|
+
_channel.close();
|
|
1920
|
+
} catch (err) {}
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// Best-effort eager cleanup at end-of-session. Snapshots can be tens or
|
|
1924
|
+
// hundreds of MB, so we don't want to rely solely on the next session's
|
|
1925
|
+
// startup to reclaim space. A single delMany() transaction is cheap to
|
|
1926
|
+
// launch and frequently commits before the page is fully torn down,
|
|
1927
|
+
// especially in Chrome/Firefox; Safari is less reliable. Anything that
|
|
1928
|
+
// doesn't complete here will be reclaimed by the next session anyway.
|
|
1929
|
+
//
|
|
1930
|
+
// Important: do NOT await any of this. The browser doesn't await async work
|
|
1931
|
+
// in the unload path; we just want the IDB transaction to be queued before
|
|
1932
|
+
// the page is killed.
|
|
1933
|
+
function attemptOwnDataDeletion() {
|
|
1934
|
+
if (ownSnapshotIds.size === 0) return;
|
|
1935
|
+
var ids = Array.from(ownSnapshotIds);
|
|
1936
|
+
ownSnapshotIds.clear();
|
|
1937
|
+
// Delete the blobs in a single transaction.
|
|
1938
|
+
try {
|
|
1939
|
+
idb.delMany(ids).catch(function() {});
|
|
1940
|
+
} catch (err) {}
|
|
1941
|
+
// Drop our entries from the index in a separate (also fire-and-forget)
|
|
1942
|
+
// transaction. If only one of the two completes, startup cleanup will
|
|
1943
|
+
// reconcile -- reclaimDeadSessionData() handles both "key gone, index
|
|
1944
|
+
// entry remains" and "index entry gone, key remains".
|
|
1945
|
+
try {
|
|
1946
|
+
updateIndex(function(index) {
|
|
1947
|
+
index.snapshots = index.snapshots.filter(function(snap) {
|
|
1948
|
+
return getSessionFromSnapshotId(snap.id) !== sessionId;
|
|
1949
|
+
});
|
|
1950
|
+
}).catch(function() {});
|
|
1951
|
+
} catch (err) {}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
function touchOwnSession() {
|
|
1955
|
+
var data = readSessionData();
|
|
1956
|
+
data[sessionId] = Date.now();
|
|
1957
|
+
writeSessionData(data);
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
function removeOwnSession() {
|
|
1961
|
+
var data = readSessionData();
|
|
1962
|
+
if (sessionId in data) {
|
|
1963
|
+
delete data[sessionId];
|
|
1964
|
+
writeSessionData(data);
|
|
1897
1965
|
}
|
|
1898
1966
|
}
|
|
1899
1967
|
|
|
1900
|
-
function
|
|
1901
|
-
|
|
1902
|
-
var
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1968
|
+
function pruneStaleSessionData() {
|
|
1969
|
+
var data = readSessionData();
|
|
1970
|
+
var now = Date.now();
|
|
1971
|
+
var changed = false;
|
|
1972
|
+
Object.keys(data).forEach(function(sid) {
|
|
1973
|
+
if (now - data[sid] > STALE_THRESHOLD_MS) {
|
|
1974
|
+
delete data[sid];
|
|
1975
|
+
changed = true;
|
|
1976
|
+
}
|
|
1977
|
+
});
|
|
1978
|
+
if (changed) writeSessionData(data);
|
|
1909
1979
|
}
|
|
1910
1980
|
|
|
1911
|
-
function
|
|
1912
|
-
|
|
1913
|
-
|
|
1981
|
+
function readSessionData() {
|
|
1982
|
+
try {
|
|
1983
|
+
var raw = window.localStorage.getItem(SESSION_DATA_KEY);
|
|
1984
|
+
var parsed = raw ? JSON.parse(raw) : null;
|
|
1985
|
+
// Tolerate the legacy array shape by ignoring it (next write replaces it).
|
|
1986
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
1987
|
+
return parsed;
|
|
1988
|
+
}
|
|
1989
|
+
return {};
|
|
1990
|
+
} catch (e) {
|
|
1991
|
+
return {};
|
|
1992
|
+
}
|
|
1914
1993
|
}
|
|
1915
1994
|
|
|
1916
|
-
function
|
|
1917
|
-
|
|
1995
|
+
function writeSessionData(obj) {
|
|
1996
|
+
try {
|
|
1997
|
+
window.localStorage.setItem(SESSION_DATA_KEY, JSON.stringify(obj));
|
|
1998
|
+
} catch (e) {} // localStorage can throw on quota exceeded; non-fatal
|
|
1918
1999
|
}
|
|
1919
2000
|
|
|
1920
2001
|
async function isStorageEnabled() {
|
|
1921
2002
|
try {
|
|
1922
|
-
|
|
2003
|
+
writeSessionData(readSessionData());
|
|
1923
2004
|
await updateIndex(function() {});
|
|
1924
2005
|
return true;
|
|
1925
2006
|
} catch(e) {
|
|
@@ -2399,10 +2480,34 @@
|
|
|
2399
2480
|
} else if (await importDataset(group, groupImportOpts)) {
|
|
2400
2481
|
importCount++;
|
|
2401
2482
|
gui.session.fileImported(group.filename, optStr);
|
|
2483
|
+
notifyMissingShapefileParts(group);
|
|
2402
2484
|
}
|
|
2403
2485
|
}
|
|
2404
2486
|
}
|
|
2405
2487
|
|
|
2488
|
+
// Surface a passive warning if a .shp file came in without its .dbf or .prj
|
|
2489
|
+
// sibling. Both files are technically optional, but their absence has very
|
|
2490
|
+
// different consequences (no attribute data, no projection metadata) and
|
|
2491
|
+
// users frequently load just the .shp by accident. Mapshaper generally
|
|
2492
|
+
// doesn't need .shx, so we don't warn about that.
|
|
2493
|
+
function notifyMissingShapefileParts(group) {
|
|
2494
|
+
if (!group.shp || !gui.notify) return;
|
|
2495
|
+
var missing = [];
|
|
2496
|
+
if (!group.dbf) missing.push({ext: '.dbf', why: 'no attribute data'});
|
|
2497
|
+
if (!group.prj) missing.push({ext: '.prj', why: 'no projection metadata'});
|
|
2498
|
+
if (!missing.length) return;
|
|
2499
|
+
var base = internal.getFileBase(group.shp.filename);
|
|
2500
|
+
var parts = missing.map(function(m) { return m.ext; }).join(' and ');
|
|
2501
|
+
var consequences = missing.map(function(m) { return m.why; }).join(', ');
|
|
2502
|
+
gui.notify({
|
|
2503
|
+
severity: 'warn',
|
|
2504
|
+
body: base + '.shp was loaded without its ' + parts +
|
|
2505
|
+
(missing.length > 1 ? ' files' : ' file') +
|
|
2506
|
+
' (' + consequences + ').',
|
|
2507
|
+
dedupKey: 'shp-missing:' + base
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2406
2511
|
async function importDataset(group, importOpts) {
|
|
2407
2512
|
var dataset;
|
|
2408
2513
|
var datasets;
|
|
@@ -2943,15 +3048,25 @@
|
|
|
2943
3048
|
|
|
2944
3049
|
function message() {
|
|
2945
3050
|
var msg = GUI.formatMessageArgs(arguments);
|
|
2946
|
-
gui.
|
|
2947
|
-
|
|
3051
|
+
if (gui.notify) {
|
|
3052
|
+
gui.notify({severity: 'info', body: msg});
|
|
3053
|
+
} else {
|
|
3054
|
+
// Fallback for early messages before MessageControl is constructed
|
|
3055
|
+
gui.message(msg);
|
|
3056
|
+
internal.logArgs(arguments);
|
|
3057
|
+
}
|
|
2948
3058
|
}
|
|
2949
3059
|
|
|
2950
|
-
//
|
|
2951
|
-
//
|
|
2952
|
-
//
|
|
3060
|
+
// CLI warnings used to surface as modal alerts, which interrupt the user
|
|
3061
|
+
// and replace any previous popup. They now go to the Messages inbox so
|
|
3062
|
+
// they can accumulate non-disruptively.
|
|
2953
3063
|
function warn() {
|
|
2954
|
-
|
|
3064
|
+
var msg = GUI.formatMessageArgs(arguments);
|
|
3065
|
+
if (gui.notify) {
|
|
3066
|
+
gui.notify({severity: 'warn', body: msg, dedupKey: 'warn:' + msg});
|
|
3067
|
+
} else {
|
|
3068
|
+
gui.alert(msg);
|
|
3069
|
+
}
|
|
2955
3070
|
}
|
|
2956
3071
|
|
|
2957
3072
|
internal.setLoggingFunctions(message, error, stop, warn);
|
|
@@ -2970,7 +3085,11 @@
|
|
|
2970
3085
|
try {
|
|
2971
3086
|
await utils$1.promisify(saveFilesToServer)(paths, data);
|
|
2972
3087
|
if (files.length >= 1) {
|
|
2973
|
-
gui.
|
|
3088
|
+
if (gui.notify) {
|
|
3089
|
+
gui.notify({severity: 'info', title: 'Saved', body: paths.join('\n')});
|
|
3090
|
+
} else {
|
|
3091
|
+
gui.alert('<b>Saved</b><br>' + paths.join('<br>'));
|
|
3092
|
+
}
|
|
2974
3093
|
}
|
|
2975
3094
|
} catch(err) {
|
|
2976
3095
|
msg = "<b>Direct save failed</b><br>Reason: " + err.message + ".";
|
|
@@ -3811,7 +3930,7 @@
|
|
|
3811
3930
|
slider.on('change', function(e) {
|
|
3812
3931
|
var pct = fromSliderPct(e.pct);
|
|
3813
3932
|
text.value(pct);
|
|
3814
|
-
pct =
|
|
3933
|
+
pct = internal.parsePercent(text.text()); // use rounded value (for consistency w/ cli)
|
|
3815
3934
|
onChange(pct);
|
|
3816
3935
|
});
|
|
3817
3936
|
slider.on('start', function(e) {
|
|
@@ -4058,11 +4177,16 @@
|
|
|
4058
4177
|
if (!_isOpen) {
|
|
4059
4178
|
btn.addClass('active');
|
|
4060
4179
|
_isOpen = true;
|
|
4061
|
-
//
|
|
4180
|
+
// Route logging output to the in-app console while it's open, so a
|
|
4181
|
+
// user typing CLI commands here sees the results inline -- the same
|
|
4182
|
+
// way a CLI user sees output in their terminal. warn() goes to a
|
|
4183
|
+
// distinct consoleWarn() rather than to the Messages inbox: warnings
|
|
4184
|
+
// emitted by command pipelines should land where the user typed,
|
|
4185
|
+
// not in a separate UI surface they might not notice.
|
|
4062
4186
|
// TODO: find a solution for logging problem when switching between multiple
|
|
4063
4187
|
// gui instances with the console open. E.g. console could close
|
|
4064
4188
|
// when an instance loses focus.
|
|
4065
|
-
internal.setLoggingFunctions(consoleMessage, consoleError, consoleStop);
|
|
4189
|
+
internal.setLoggingFunctions(consoleMessage, consoleError, consoleStop, consoleWarn);
|
|
4066
4190
|
gui.container.addClass('console-open');
|
|
4067
4191
|
el.show();
|
|
4068
4192
|
gui.dispatchEvent('resize');
|
|
@@ -4460,6 +4584,15 @@
|
|
|
4460
4584
|
toLog(msg, 'console-error');
|
|
4461
4585
|
}
|
|
4462
4586
|
|
|
4587
|
+
// Routed from internal.warn() while the console is open. Distinct from
|
|
4588
|
+
// consoleWarning() (which is used for hard-stop errors) so the styling
|
|
4589
|
+
// can differ -- warnings shouldn't read as red "something went wrong",
|
|
4590
|
+
// they're just heads-ups about how the input was interpreted.
|
|
4591
|
+
function consoleWarn() {
|
|
4592
|
+
var msg = GUI.formatMessageArgs(arguments);
|
|
4593
|
+
toLog(msg, 'console-warn');
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4463
4596
|
function consoleMessage() {
|
|
4464
4597
|
var msg = GUI.formatMessageArgs(arguments);
|
|
4465
4598
|
if (internal.loggingEnabled()) {
|
|
@@ -4710,7 +4843,7 @@
|
|
|
4710
4843
|
});
|
|
4711
4844
|
|
|
4712
4845
|
internal.writeFiles = function() {
|
|
4713
|
-
error$
|
|
4846
|
+
error$2(unsupportedMsg);
|
|
4714
4847
|
};
|
|
4715
4848
|
return;
|
|
4716
4849
|
}
|
|
@@ -6825,1356 +6958,1386 @@
|
|
|
6825
6958
|
// Fall back to browserify's Buffer polyfill
|
|
6826
6959
|
var B = typeof Buffer != 'undefined' ? Buffer : require$1('buffer').Buffer;
|
|
6827
6960
|
|
|
6828
|
-
//
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
var stash = {};
|
|
6832
|
-
|
|
6833
|
-
function stashVar(key, val) {
|
|
6834
|
-
if (key in stash) {
|
|
6835
|
-
error('Tried to replace a stashed variable:', key);
|
|
6836
|
-
}
|
|
6837
|
-
stash[key] = val;
|
|
6961
|
+
// We do NOT import from mapshaper-logging here to avoid a circular dependency
|
|
6962
|
+
function error$1() {
|
|
6963
|
+
throw new Error(Array.prototype.slice.call(arguments).join(' '));
|
|
6838
6964
|
}
|
|
6839
6965
|
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
// error('Tried to read a nonexistent variable from the stash:', key);
|
|
6844
|
-
}
|
|
6845
|
-
return stash[key];
|
|
6966
|
+
var uniqCount = 0;
|
|
6967
|
+
function getUniqueName(prefix) {
|
|
6968
|
+
return (prefix || "__id_") + (++uniqCount);
|
|
6846
6969
|
}
|
|
6847
6970
|
|
|
6848
|
-
function
|
|
6849
|
-
|
|
6971
|
+
function isFunction(obj) {
|
|
6972
|
+
return typeof obj == 'function';
|
|
6850
6973
|
}
|
|
6851
6974
|
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6975
|
+
function isPromise(arg) {
|
|
6976
|
+
return arg ? isFunction(arg.then) : false;
|
|
6977
|
+
}
|
|
6855
6978
|
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
}
|
|
6979
|
+
function isObject(obj) {
|
|
6980
|
+
return obj === Object(obj); // via underscore
|
|
6981
|
+
}
|
|
6859
6982
|
|
|
6860
|
-
|
|
6983
|
+
function clamp(val, min, max) {
|
|
6984
|
+
return val < min ? min : (val > max ? max : val);
|
|
6985
|
+
}
|
|
6861
6986
|
|
|
6862
|
-
|
|
6987
|
+
function isArray(obj) {
|
|
6988
|
+
return Array.isArray(obj);
|
|
6989
|
+
}
|
|
6863
6990
|
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
return
|
|
6867
|
-
setLoggingFunctions(m, e, s, w);
|
|
6868
|
-
};
|
|
6991
|
+
// Is obj a valid number or NaN? (test if obj is type number)
|
|
6992
|
+
function isNumber(obj) {
|
|
6993
|
+
return obj != null && obj.constructor == Number;
|
|
6869
6994
|
}
|
|
6870
6995
|
|
|
6871
|
-
function
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
}
|
|
6996
|
+
function isValidNumber(val) {
|
|
6997
|
+
return isNumber(val) && !isNaN(val);
|
|
6998
|
+
}
|
|
6875
6999
|
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
7000
|
+
// Similar to isFinite() but does not coerce strings or other types
|
|
7001
|
+
function isFiniteNumber(val) {
|
|
7002
|
+
return isValidNumber(val) && val !== Infinity && val !== -Infinity;
|
|
7003
|
+
}
|
|
6880
7004
|
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
7005
|
+
// This uses type conversion
|
|
7006
|
+
// export function isFiniteNumber(val) {
|
|
7007
|
+
// return val > -Infinity && val < Infinity;
|
|
7008
|
+
// }
|
|
6884
7009
|
|
|
6885
|
-
|
|
6886
|
-
|
|
7010
|
+
function isNonNegNumber(val) {
|
|
7011
|
+
return isNumber(val) && val >= 0;
|
|
7012
|
+
}
|
|
6887
7013
|
|
|
6888
|
-
|
|
7014
|
+
function isInteger(obj) {
|
|
7015
|
+
return isNumber(obj) && ((obj | 0) === obj);
|
|
6889
7016
|
}
|
|
6890
7017
|
|
|
6891
|
-
function
|
|
6892
|
-
|
|
7018
|
+
function isEven(obj) {
|
|
7019
|
+
return (obj % 2) === 0;
|
|
6893
7020
|
}
|
|
6894
7021
|
|
|
6895
|
-
function
|
|
6896
|
-
|
|
7022
|
+
function isOdd(obj) {
|
|
7023
|
+
return (obj % 2) === 1;
|
|
6897
7024
|
}
|
|
6898
7025
|
|
|
6899
|
-
function
|
|
6900
|
-
return
|
|
7026
|
+
function isString(obj) {
|
|
7027
|
+
return obj != null && obj.toString === String.prototype.toString;
|
|
7028
|
+
// TODO: replace w/ something better.
|
|
6901
7029
|
}
|
|
6902
7030
|
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
_error.apply(null, utils.toArray(arguments));
|
|
7031
|
+
function isDate(obj) {
|
|
7032
|
+
return !!obj && obj.getTime === Date.prototype.getTime;
|
|
6906
7033
|
}
|
|
6907
7034
|
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
// _stop.apply(null, utils.toArray(arguments));
|
|
6911
|
-
_stop.apply(null, messageArgs(arguments));
|
|
7035
|
+
function isBoolean(obj) {
|
|
7036
|
+
return obj === true || obj === false;
|
|
6912
7037
|
}
|
|
6913
7038
|
|
|
6914
|
-
function
|
|
6915
|
-
|
|
7039
|
+
function formatDateISO(d) {
|
|
7040
|
+
if (!isDate(d)) return '';
|
|
7041
|
+
return d.toISOString().replace(':00.000Z', 'Z');
|
|
6916
7042
|
}
|
|
6917
7043
|
|
|
6918
|
-
//
|
|
6919
|
-
function
|
|
6920
|
-
|
|
7044
|
+
// Convert an array-like object to an Array, or make a copy if @obj is an Array
|
|
7045
|
+
function toArray(obj) {
|
|
7046
|
+
var arr;
|
|
7047
|
+
if (!isArrayLike(obj)) error$1("toArray() requires an array-like object");
|
|
7048
|
+
try {
|
|
7049
|
+
arr = Array.prototype.slice.call(obj, 0); // breaks in ie8
|
|
7050
|
+
} catch(e) {
|
|
7051
|
+
// support ie8
|
|
7052
|
+
arr = [];
|
|
7053
|
+
for (var i=0, n=obj.length; i<n; i++) {
|
|
7054
|
+
arr[i] = obj[i];
|
|
7055
|
+
}
|
|
7056
|
+
}
|
|
7057
|
+
return arr;
|
|
6921
7058
|
}
|
|
6922
7059
|
|
|
6923
|
-
|
|
6924
|
-
|
|
7060
|
+
// Array like: has length property, is numerically indexed and mutable.
|
|
7061
|
+
// TODO: try to detect objects with length property but no indexed data elements
|
|
7062
|
+
function isArrayLike(obj) {
|
|
7063
|
+
if (!obj) return false;
|
|
7064
|
+
if (isArray(obj)) return true;
|
|
7065
|
+
if (isString(obj)) return false;
|
|
7066
|
+
if (obj.length === 0 || obj.length > 0) return true;
|
|
7067
|
+
return false;
|
|
6925
7068
|
}
|
|
6926
7069
|
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
onceMessages.push(str);
|
|
6931
|
-
_warn.apply(null, messageArgs(arguments));
|
|
7070
|
+
// See https://raw.github.com/kvz/phpjs/master/functions/strings/addslashes.js
|
|
7071
|
+
function addslashes(str) {
|
|
7072
|
+
return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
|
|
6932
7073
|
}
|
|
6933
7074
|
|
|
6934
|
-
//
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
_stop = stop;
|
|
6939
|
-
_warn = warn;
|
|
7075
|
+
// Escape a literal string to use in a regexp.
|
|
7076
|
+
// Ref.: http://simonwillison.net/2006/Jan/20/escape/
|
|
7077
|
+
function regexEscape(str) {
|
|
7078
|
+
return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
|
6940
7079
|
}
|
|
6941
7080
|
|
|
6942
|
-
// get detailed error information from error stack (if available)
|
|
6943
|
-
// Example stack string (Node.js):
|
|
6944
|
-
/*
|
|
6945
|
-
/Users/someuser/somescript.js:226
|
|
6946
|
-
opacity: Math.round(weight * 5 / 5 // 0.2 0.4 0.6 etc
|
|
6947
|
-
^
|
|
6948
7081
|
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
7082
|
+
// See https://github.com/janl/mustache.js/blob/master/mustache.js
|
|
7083
|
+
var entityMap = {
|
|
7084
|
+
'&': '&',
|
|
7085
|
+
'<': '<',
|
|
7086
|
+
'>': '>',
|
|
7087
|
+
'"': '"',
|
|
7088
|
+
"'": ''',
|
|
7089
|
+
'/': '/'
|
|
7090
|
+
};
|
|
7091
|
+
function htmlEscape(s) {
|
|
7092
|
+
return String(s).replace(/[&<>"'/]/g, function(s) {
|
|
7093
|
+
return entityMap[s];
|
|
7094
|
+
});
|
|
6961
7095
|
}
|
|
6962
7096
|
|
|
6963
|
-
// print a message to stdout
|
|
6964
|
-
function print() {
|
|
6965
|
-
STDOUT = true; // tell logArgs() to print to stdout, not stderr
|
|
6966
|
-
// calling message() adds the "[command name]" prefix
|
|
6967
|
-
_message(utils.toArray(arguments));
|
|
6968
|
-
STDOUT = false;
|
|
6969
|
-
}
|
|
6970
7097
|
|
|
6971
|
-
function
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
7098
|
+
function defaults(dest) {
|
|
7099
|
+
for (var i=1, n=arguments.length; i<n; i++) {
|
|
7100
|
+
var src = arguments[i] || {};
|
|
7101
|
+
for (var key in src) {
|
|
7102
|
+
if (key in dest === false && src.hasOwnProperty(key)) {
|
|
7103
|
+
dest[key] = src[key];
|
|
7104
|
+
}
|
|
7105
|
+
}
|
|
6975
7106
|
}
|
|
7107
|
+
return dest;
|
|
6976
7108
|
}
|
|
6977
7109
|
|
|
6978
|
-
function
|
|
6979
|
-
|
|
7110
|
+
function extend(o) {
|
|
7111
|
+
var dest = o || {},
|
|
7112
|
+
n = arguments.length,
|
|
7113
|
+
key, i, src;
|
|
7114
|
+
for (i=1; i<n; i++) {
|
|
7115
|
+
src = arguments[i] || {};
|
|
7116
|
+
for (key in src) {
|
|
7117
|
+
if (src.hasOwnProperty(key)) {
|
|
7118
|
+
dest[key] = src[key];
|
|
7119
|
+
}
|
|
7120
|
+
}
|
|
7121
|
+
}
|
|
7122
|
+
return dest;
|
|
6980
7123
|
}
|
|
6981
7124
|
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
7125
|
+
// Pseudoclassical inheritance
|
|
7126
|
+
//
|
|
7127
|
+
// Inherit from a Parent function:
|
|
7128
|
+
// inherit(Child, Parent);
|
|
7129
|
+
// Call parent's constructor (inside child constructor):
|
|
7130
|
+
// this.__super__([args...]);
|
|
7131
|
+
function inherit(targ, src) {
|
|
7132
|
+
var f = function() {
|
|
7133
|
+
if (this.__super__ == f) {
|
|
7134
|
+
// add __super__ of parent to front of lookup chain
|
|
7135
|
+
// so parent class constructor can call its parent using this.__super__
|
|
7136
|
+
this.__super__ = src.prototype.__super__;
|
|
7137
|
+
// call parent constructor function. this.__super__ now points to parent-of-parent
|
|
7138
|
+
src.apply(this, arguments);
|
|
7139
|
+
// remove temp __super__, expose targ.prototype.__super__ again
|
|
7140
|
+
delete this.__super__;
|
|
7141
|
+
}
|
|
7142
|
+
};
|
|
6985
7143
|
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
7144
|
+
f.prototype = src.prototype || src; // added || src to allow inheriting from objects as well as functions
|
|
7145
|
+
// Extend targ prototype instead of wiping it out --
|
|
7146
|
+
// in case inherit() is called after targ.prototype = {stuff}; statement
|
|
7147
|
+
targ.prototype = extend(new f(), targ.prototype); //
|
|
7148
|
+
targ.prototype.constructor = targ;
|
|
7149
|
+
targ.prototype.__super__ = f;
|
|
6990
7150
|
}
|
|
6991
7151
|
|
|
6992
|
-
function
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
7152
|
+
function promisify(asyncFn) {
|
|
7153
|
+
return function() {
|
|
7154
|
+
var args = toArray(arguments);
|
|
7155
|
+
return new Promise((resolve, reject) => {
|
|
7156
|
+
var cb = function(err, data) {
|
|
7157
|
+
if (err) reject(err);
|
|
7158
|
+
else resolve(data);
|
|
7159
|
+
};
|
|
7160
|
+
args.push(cb);
|
|
7161
|
+
asyncFn.apply(this, args);
|
|
7162
|
+
});
|
|
7163
|
+
};
|
|
6996
7164
|
}
|
|
6997
7165
|
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7166
|
+
function runAsync(fn, arg) {
|
|
7167
|
+
return new Promise((resolve, reject) => {
|
|
7168
|
+
fn(arg, function(err, data) {
|
|
7169
|
+
return err ? reject(err) : resolve(data);
|
|
7170
|
+
});
|
|
7171
|
+
});
|
|
7001
7172
|
}
|
|
7002
|
-
}
|
|
7003
7173
|
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7174
|
+
// Call @iter on each member of an array (similar to Array#reduce(iter))
|
|
7175
|
+
// iter: function(memo, item, callback)
|
|
7176
|
+
// Call @done when all members have been processed or if an error occurs
|
|
7177
|
+
// done: function(err, memo)
|
|
7178
|
+
// @memo: Initial value
|
|
7179
|
+
//
|
|
7180
|
+
function reduceAsync(arr, memo, iter, done) {
|
|
7181
|
+
var call = typeof setImmediate == 'undefined' ? setTimeout : setImmediate;
|
|
7182
|
+
var i=0;
|
|
7183
|
+
next(null, memo);
|
|
7184
|
+
|
|
7185
|
+
function next(err, memo) {
|
|
7186
|
+
// Detach next operation from call stack to prevent overflow
|
|
7187
|
+
// Don't use setTimeout(, 0) if setImmediate is available
|
|
7188
|
+
// (setTimeout() can introduce a long delay if previous operation was slow,
|
|
7189
|
+
// as of Node 0.10.32 -- a bug?)
|
|
7190
|
+
if (err) {
|
|
7191
|
+
return done(err, null);
|
|
7016
7192
|
}
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7193
|
+
call(function() {
|
|
7194
|
+
if (i < arr.length === false) {
|
|
7195
|
+
done(null, memo);
|
|
7196
|
+
} else {
|
|
7197
|
+
iter(memo, arr[i++], next);
|
|
7198
|
+
}
|
|
7199
|
+
}, 0);
|
|
7023
7200
|
}
|
|
7024
7201
|
}
|
|
7025
7202
|
|
|
7026
|
-
function UserError(msg) {
|
|
7027
|
-
var err = new Error(msg);
|
|
7028
|
-
err.name = 'UserError';
|
|
7029
|
-
return err;
|
|
7030
|
-
}
|
|
7031
7203
|
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7204
|
+
// Append elements of @src array to @dest array
|
|
7205
|
+
function merge(dest, src) {
|
|
7206
|
+
if (!isArray(dest) || !isArray(src)) {
|
|
7207
|
+
error$1("Usage: merge(destArray, srcArray);");
|
|
7208
|
+
}
|
|
7209
|
+
for (var i=0, n=src.length; i<n; i++) {
|
|
7210
|
+
dest.push(src[i]);
|
|
7211
|
+
}
|
|
7212
|
+
return dest;
|
|
7036
7213
|
}
|
|
7037
7214
|
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
line = line.map(function(str, i) {
|
|
7046
|
-
var rt = alignments && alignments[i] == 'right';
|
|
7047
|
-
var pad = (rt ? str.padStart : str.padEnd).bind(str);
|
|
7048
|
-
return pad(widths[i], ' ');
|
|
7049
|
-
});
|
|
7050
|
-
return ' ' + line.join(' ');
|
|
7051
|
-
}).join('\n');
|
|
7215
|
+
// Returns elements in arr and not in other
|
|
7216
|
+
// (similar to underscore diff)
|
|
7217
|
+
function difference(arr, other) {
|
|
7218
|
+
var index = arrayToIndex(other);
|
|
7219
|
+
return arr.filter(function(el) {
|
|
7220
|
+
return !Object.prototype.hasOwnProperty.call(index, el);
|
|
7221
|
+
});
|
|
7052
7222
|
}
|
|
7053
7223
|
|
|
7054
|
-
//
|
|
7055
|
-
function
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
}, 0),
|
|
7060
|
-
colWidth = longest + 2,
|
|
7061
|
-
perLine = Math.floor((width || 80) / colWidth) || 1;
|
|
7062
|
-
return arr.reduce(function(memo, name, i) {
|
|
7063
|
-
var col = i % perLine;
|
|
7064
|
-
if (i > 0 && col === 0) memo += '\n';
|
|
7065
|
-
if (col < perLine - 1) { // right-pad all but rightmost column
|
|
7066
|
-
name = utils.rpad(name, colWidth - 2, ' ');
|
|
7067
|
-
}
|
|
7068
|
-
return memo + ' ' + name;
|
|
7069
|
-
}, '');
|
|
7224
|
+
// Return the intersection of two arrays
|
|
7225
|
+
function intersection(a, b) {
|
|
7226
|
+
return a.filter(function(el) {
|
|
7227
|
+
return b.includes(el);
|
|
7228
|
+
});
|
|
7070
7229
|
}
|
|
7071
7230
|
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7231
|
+
function indexOf(arr, item) {
|
|
7232
|
+
var nan = item !== item;
|
|
7233
|
+
for (var i = 0, len = arr.length || 0; i < len; i++) {
|
|
7234
|
+
if (arr[i] === item) return i;
|
|
7235
|
+
if (nan && arr[i] !== arr[i]) return i;
|
|
7236
|
+
}
|
|
7237
|
+
return -1;
|
|
7075
7238
|
}
|
|
7076
7239
|
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
arr.unshift('[' + cmd + ']');
|
|
7240
|
+
// Test a string or array-like object for existence of substring or element
|
|
7241
|
+
function contains(container, item) {
|
|
7242
|
+
if (isString(container)) {
|
|
7243
|
+
return container.indexOf(item) != -1;
|
|
7082
7244
|
}
|
|
7083
|
-
|
|
7245
|
+
else if (isArrayLike(container)) {
|
|
7246
|
+
return indexOf(container, item) != -1;
|
|
7247
|
+
}
|
|
7248
|
+
error$1("Expected Array or String argument");
|
|
7084
7249
|
}
|
|
7085
7250
|
|
|
7086
|
-
function
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
else console.error(msg);
|
|
7251
|
+
function some(arr, test) {
|
|
7252
|
+
return arr.reduce(function(val, item) {
|
|
7253
|
+
return val || test(item); // TODO: short-circuit?
|
|
7254
|
+
}, false);
|
|
7091
7255
|
}
|
|
7092
7256
|
|
|
7093
|
-
function
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
}
|
|
7098
|
-
return str;
|
|
7257
|
+
function every(arr, test) {
|
|
7258
|
+
return arr.reduce(function(val, item) {
|
|
7259
|
+
return val && test(item);
|
|
7260
|
+
}, true);
|
|
7099
7261
|
}
|
|
7100
7262
|
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
return
|
|
7263
|
+
function find(arr, test, ctx) {
|
|
7264
|
+
var matches = arr.filter(test, ctx);
|
|
7265
|
+
return matches.length === 0 ? null : matches[0];
|
|
7104
7266
|
}
|
|
7105
7267
|
|
|
7106
|
-
function
|
|
7107
|
-
|
|
7268
|
+
function range(len, start, inc) {
|
|
7269
|
+
var arr = [],
|
|
7270
|
+
v = start === void 0 ? 0 : start,
|
|
7271
|
+
i = inc === void 0 ? 1 : inc;
|
|
7272
|
+
while(len--) {
|
|
7273
|
+
arr.push(v);
|
|
7274
|
+
v += i;
|
|
7275
|
+
}
|
|
7276
|
+
return arr;
|
|
7108
7277
|
}
|
|
7109
7278
|
|
|
7110
|
-
function
|
|
7111
|
-
|
|
7279
|
+
function repeat(times, func) {
|
|
7280
|
+
var values = [],
|
|
7281
|
+
val;
|
|
7282
|
+
for (var i=0; i<times; i++) {
|
|
7283
|
+
val = func(i);
|
|
7284
|
+
if (val !== void 0) {
|
|
7285
|
+
values[i] = val;
|
|
7286
|
+
}
|
|
7287
|
+
}
|
|
7288
|
+
return values.length > 0 ? values : void 0;
|
|
7112
7289
|
}
|
|
7113
7290
|
|
|
7114
|
-
|
|
7115
|
-
|
|
7291
|
+
// Calc sum, skip falsy and NaN values
|
|
7292
|
+
// Assumes: no other non-numeric objects in array
|
|
7293
|
+
//
|
|
7294
|
+
function sum(arr, info) {
|
|
7295
|
+
if (!isArrayLike(arr)) error$1 ("sum() expects an array, received:", arr);
|
|
7296
|
+
var tot = 0,
|
|
7297
|
+
nan = 0,
|
|
7298
|
+
val;
|
|
7299
|
+
for (var i=0, n=arr.length; i<n; i++) {
|
|
7300
|
+
val = arr[i];
|
|
7301
|
+
if (val) {
|
|
7302
|
+
tot += val;
|
|
7303
|
+
} else if (isNaN(val)) {
|
|
7304
|
+
nan++;
|
|
7305
|
+
}
|
|
7306
|
+
}
|
|
7307
|
+
if (info) {
|
|
7308
|
+
info.nan = nan;
|
|
7309
|
+
}
|
|
7310
|
+
return tot;
|
|
7116
7311
|
}
|
|
7117
7312
|
|
|
7118
|
-
|
|
7119
|
-
|
|
7313
|
+
// Calculate min and max values of an array, ignoring NaN values
|
|
7314
|
+
function getArrayBounds(arr) {
|
|
7315
|
+
var min = Infinity,
|
|
7316
|
+
max = -Infinity,
|
|
7317
|
+
nan = 0, val;
|
|
7318
|
+
for (var i=0, len=arr.length; i<len; i++) {
|
|
7319
|
+
val = arr[i];
|
|
7320
|
+
if (val !== val) nan++;
|
|
7321
|
+
if (val < min) min = val;
|
|
7322
|
+
if (val > max) max = val;
|
|
7323
|
+
}
|
|
7324
|
+
return {
|
|
7325
|
+
min: min,
|
|
7326
|
+
max: max,
|
|
7327
|
+
nan: nan
|
|
7328
|
+
};
|
|
7120
7329
|
}
|
|
7121
7330
|
|
|
7122
|
-
function
|
|
7123
|
-
|
|
7124
|
-
|
|
7331
|
+
// export function uniq(src) {
|
|
7332
|
+
// var index = {};
|
|
7333
|
+
// return src.reduce(function(memo, el) {
|
|
7334
|
+
// if (el in index === false) {
|
|
7335
|
+
// index[el] = true;
|
|
7336
|
+
// memo.push(el);
|
|
7337
|
+
// }
|
|
7338
|
+
// return memo;
|
|
7339
|
+
// }, []);
|
|
7340
|
+
// }
|
|
7125
7341
|
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7342
|
+
function uniq(src) {
|
|
7343
|
+
var index = new Set();
|
|
7344
|
+
var arr = [];
|
|
7345
|
+
var item;
|
|
7346
|
+
for (var i=0, n=src.length; i<n; i++) {
|
|
7347
|
+
item = src[i];
|
|
7348
|
+
if (!index.has(item)) {
|
|
7349
|
+
arr.push(item);
|
|
7350
|
+
index.add(item);
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7353
|
+
return arr;
|
|
7129
7354
|
}
|
|
7130
7355
|
|
|
7131
|
-
function
|
|
7132
|
-
return
|
|
7356
|
+
function pluck(arr, key) {
|
|
7357
|
+
return arr.map(function(obj) {
|
|
7358
|
+
return obj[key];
|
|
7359
|
+
});
|
|
7133
7360
|
}
|
|
7134
7361
|
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7362
|
+
function countValues(arr) {
|
|
7363
|
+
return arr.reduce(function(memo, val) {
|
|
7364
|
+
memo[val] = (val in memo) ? memo[val] + 1 : 1;
|
|
7365
|
+
return memo;
|
|
7366
|
+
}, {});
|
|
7138
7367
|
}
|
|
7139
7368
|
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
function isNonNegNumber(val) {
|
|
7146
|
-
return isNumber(val) && val >= 0;
|
|
7369
|
+
function indexOn(arr, k) {
|
|
7370
|
+
return arr.reduce(function(index, o) {
|
|
7371
|
+
index[o[k]] = o;
|
|
7372
|
+
return index;
|
|
7373
|
+
}, {});
|
|
7147
7374
|
}
|
|
7148
7375
|
|
|
7149
|
-
function
|
|
7150
|
-
return
|
|
7376
|
+
function groupBy(arr, k) {
|
|
7377
|
+
return arr.reduce(function(index, o) {
|
|
7378
|
+
var keyval = o[k];
|
|
7379
|
+
if (keyval in index) {
|
|
7380
|
+
index[keyval].push(o);
|
|
7381
|
+
} else {
|
|
7382
|
+
index[keyval] = [o];
|
|
7383
|
+
}
|
|
7384
|
+
return index;
|
|
7385
|
+
}, {});
|
|
7151
7386
|
}
|
|
7152
7387
|
|
|
7153
|
-
function
|
|
7154
|
-
|
|
7388
|
+
function arrayToIndex(arr, val) {
|
|
7389
|
+
var init = arguments.length > 1;
|
|
7390
|
+
return arr.reduce(function(index, key) {
|
|
7391
|
+
index[key] = init ? val : true;
|
|
7392
|
+
return index;
|
|
7393
|
+
}, {});
|
|
7155
7394
|
}
|
|
7156
7395
|
|
|
7157
|
-
|
|
7158
|
-
|
|
7396
|
+
// Support for iterating over array-like objects, like typed arrays
|
|
7397
|
+
function forEach(arr, func, ctx) {
|
|
7398
|
+
if (!isArrayLike(arr)) {
|
|
7399
|
+
throw new Error("#forEach() takes an array-like argument. " + arr);
|
|
7400
|
+
}
|
|
7401
|
+
for (var i=0, n=arr.length; i < n; i++) {
|
|
7402
|
+
func.call(ctx, arr[i], i);
|
|
7403
|
+
}
|
|
7159
7404
|
}
|
|
7160
7405
|
|
|
7161
|
-
function
|
|
7162
|
-
|
|
7163
|
-
|
|
7406
|
+
function forEachProperty(o, func, ctx) {
|
|
7407
|
+
Object.keys(o).forEach(function(key) {
|
|
7408
|
+
func.call(ctx, o[key], key);
|
|
7409
|
+
});
|
|
7164
7410
|
}
|
|
7165
7411
|
|
|
7166
|
-
function
|
|
7167
|
-
|
|
7412
|
+
function initializeArray(arr, init) {
|
|
7413
|
+
for (var i=0, len=arr.length; i<len; i++) {
|
|
7414
|
+
arr[i] = init;
|
|
7415
|
+
}
|
|
7416
|
+
return arr;
|
|
7168
7417
|
}
|
|
7169
7418
|
|
|
7170
|
-
function
|
|
7171
|
-
|
|
7419
|
+
function replaceArray(arr, arr2) {
|
|
7420
|
+
arr.splice(0, arr.length);
|
|
7421
|
+
for (var i=0, n=arr2.length; i<n; i++) {
|
|
7422
|
+
arr.push(arr2[i]);
|
|
7423
|
+
}
|
|
7172
7424
|
}
|
|
7173
7425
|
|
|
7174
|
-
function
|
|
7175
|
-
|
|
7176
|
-
|
|
7426
|
+
function repeatString(src, n) {
|
|
7427
|
+
var str = "";
|
|
7428
|
+
for (var i=0; i<n; i++)
|
|
7429
|
+
str += src;
|
|
7430
|
+
return str;
|
|
7177
7431
|
}
|
|
7178
7432
|
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
var arr;
|
|
7182
|
-
if (!isArrayLike(obj)) error("toArray() requires an array-like object");
|
|
7183
|
-
try {
|
|
7184
|
-
arr = Array.prototype.slice.call(obj, 0); // breaks in ie8
|
|
7185
|
-
} catch(e) {
|
|
7186
|
-
// support ie8
|
|
7187
|
-
arr = [];
|
|
7188
|
-
for (var i=0, n=obj.length; i<n; i++) {
|
|
7189
|
-
arr[i] = obj[i];
|
|
7190
|
-
}
|
|
7191
|
-
}
|
|
7192
|
-
return arr;
|
|
7433
|
+
function splitLines(str) {
|
|
7434
|
+
return str.split(/\r?\n/);
|
|
7193
7435
|
}
|
|
7194
7436
|
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
function isArrayLike(obj) {
|
|
7198
|
-
if (!obj) return false;
|
|
7199
|
-
if (isArray(obj)) return true;
|
|
7200
|
-
if (isString(obj)) return false;
|
|
7201
|
-
if (obj.length === 0 || obj.length > 0) return true;
|
|
7202
|
-
return false;
|
|
7437
|
+
function pluralSuffix(count) {
|
|
7438
|
+
return count != 1 ? 's' : '';
|
|
7203
7439
|
}
|
|
7204
7440
|
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
|
|
7441
|
+
function endsWith(str, ending) {
|
|
7442
|
+
return str.indexOf(ending, str.length - ending.length) !== -1;
|
|
7208
7443
|
}
|
|
7209
7444
|
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
return str.
|
|
7445
|
+
function lpad(str, size, pad) {
|
|
7446
|
+
pad = pad || ' ';
|
|
7447
|
+
str = String(str);
|
|
7448
|
+
return repeatString(pad, size - str.length) + str;
|
|
7214
7449
|
}
|
|
7215
7450
|
|
|
7451
|
+
function rpad(str, size, pad) {
|
|
7452
|
+
pad = pad || ' ';
|
|
7453
|
+
str = String(str);
|
|
7454
|
+
return str + repeatString(pad, size - str.length);
|
|
7455
|
+
}
|
|
7216
7456
|
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
'&': '&',
|
|
7220
|
-
'<': '<',
|
|
7221
|
-
'>': '>',
|
|
7222
|
-
'"': '"',
|
|
7223
|
-
"'": ''',
|
|
7224
|
-
'/': '/'
|
|
7225
|
-
};
|
|
7226
|
-
function htmlEscape(s) {
|
|
7227
|
-
return String(s).replace(/[&<>"'/]/g, function(s) {
|
|
7228
|
-
return entityMap[s];
|
|
7229
|
-
});
|
|
7457
|
+
function trim(str) {
|
|
7458
|
+
return ltrim(rtrim(str));
|
|
7230
7459
|
}
|
|
7231
7460
|
|
|
7461
|
+
var ltrimRxp = /^\s+/;
|
|
7462
|
+
function ltrim(str) {
|
|
7463
|
+
return str.replace(ltrimRxp, '');
|
|
7464
|
+
}
|
|
7232
7465
|
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
for (var key in src) {
|
|
7237
|
-
if (key in dest === false && src.hasOwnProperty(key)) {
|
|
7238
|
-
dest[key] = src[key];
|
|
7239
|
-
}
|
|
7240
|
-
}
|
|
7241
|
-
}
|
|
7242
|
-
return dest;
|
|
7466
|
+
var rtrimRxp = /\s+$/;
|
|
7467
|
+
function rtrim(str) {
|
|
7468
|
+
return str.replace(rtrimRxp, '');
|
|
7243
7469
|
}
|
|
7244
7470
|
|
|
7245
|
-
function
|
|
7246
|
-
var
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
}
|
|
7471
|
+
function addThousandsSep(str) {
|
|
7472
|
+
var fmt = '',
|
|
7473
|
+
start = str[0] == '-' ? 1 : 0,
|
|
7474
|
+
dec = str.indexOf('.'),
|
|
7475
|
+
end = str.length,
|
|
7476
|
+
ins = (dec == -1 ? end : dec) - 3;
|
|
7477
|
+
while (ins > start) {
|
|
7478
|
+
fmt = ',' + str.substring(ins, end) + fmt;
|
|
7479
|
+
end = ins;
|
|
7480
|
+
ins -= 3;
|
|
7256
7481
|
}
|
|
7257
|
-
return
|
|
7482
|
+
return str.substring(0, end) + fmt;
|
|
7258
7483
|
}
|
|
7259
7484
|
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
// Inherit from a Parent function:
|
|
7263
|
-
// inherit(Child, Parent);
|
|
7264
|
-
// Call parent's constructor (inside child constructor):
|
|
7265
|
-
// this.__super__([args...]);
|
|
7266
|
-
function inherit(targ, src) {
|
|
7267
|
-
var f = function() {
|
|
7268
|
-
if (this.__super__ == f) {
|
|
7269
|
-
// add __super__ of parent to front of lookup chain
|
|
7270
|
-
// so parent class constructor can call its parent using this.__super__
|
|
7271
|
-
this.__super__ = src.prototype.__super__;
|
|
7272
|
-
// call parent constructor function. this.__super__ now points to parent-of-parent
|
|
7273
|
-
src.apply(this, arguments);
|
|
7274
|
-
// remove temp __super__, expose targ.prototype.__super__ again
|
|
7275
|
-
delete this.__super__;
|
|
7276
|
-
}
|
|
7277
|
-
};
|
|
7278
|
-
|
|
7279
|
-
f.prototype = src.prototype || src; // added || src to allow inheriting from objects as well as functions
|
|
7280
|
-
// Extend targ prototype instead of wiping it out --
|
|
7281
|
-
// in case inherit() is called after targ.prototype = {stuff}; statement
|
|
7282
|
-
targ.prototype = extend(new f(), targ.prototype); //
|
|
7283
|
-
targ.prototype.constructor = targ;
|
|
7284
|
-
targ.prototype.__super__ = f;
|
|
7485
|
+
function numToStr(num, decimals) {
|
|
7486
|
+
return decimals >= 0 ? num.toFixed(decimals) : String(num);
|
|
7285
7487
|
}
|
|
7286
7488
|
|
|
7287
|
-
function
|
|
7288
|
-
return
|
|
7289
|
-
var args = toArray(arguments);
|
|
7290
|
-
return new Promise((resolve, reject) => {
|
|
7291
|
-
var cb = function(err, data) {
|
|
7292
|
-
if (err) reject(err);
|
|
7293
|
-
else resolve(data);
|
|
7294
|
-
};
|
|
7295
|
-
args.push(cb);
|
|
7296
|
-
asyncFn.apply(this, args);
|
|
7297
|
-
});
|
|
7298
|
-
};
|
|
7489
|
+
function formatNumber(val) {
|
|
7490
|
+
return val + '';
|
|
7299
7491
|
}
|
|
7300
7492
|
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
});
|
|
7306
|
-
});
|
|
7307
|
-
}
|
|
7308
|
-
|
|
7309
|
-
// Call @iter on each member of an array (similar to Array#reduce(iter))
|
|
7310
|
-
// iter: function(memo, item, callback)
|
|
7311
|
-
// Call @done when all members have been processed or if an error occurs
|
|
7312
|
-
// done: function(err, memo)
|
|
7313
|
-
// @memo: Initial value
|
|
7314
|
-
//
|
|
7315
|
-
function reduceAsync(arr, memo, iter, done) {
|
|
7316
|
-
var call = typeof setImmediate == 'undefined' ? setTimeout : setImmediate;
|
|
7317
|
-
var i=0;
|
|
7318
|
-
next(null, memo);
|
|
7493
|
+
function formatIntlNumber(val) {
|
|
7494
|
+
var str = formatNumber(val);
|
|
7495
|
+
return '"' + str.replace('.', ',') + '"'; // need to quote if comma-delimited
|
|
7496
|
+
}
|
|
7319
7497
|
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7498
|
+
function formatNumberForDisplay(num, decimals, nullStr, showPos) {
|
|
7499
|
+
var fmt;
|
|
7500
|
+
if (isNaN(num)) {
|
|
7501
|
+
fmt = nullStr || '-';
|
|
7502
|
+
} else {
|
|
7503
|
+
fmt = numToStr(num, decimals);
|
|
7504
|
+
fmt = addThousandsSep(fmt);
|
|
7505
|
+
if (showPos && parseFloat(fmt) > 0) {
|
|
7506
|
+
fmt = "+" + fmt;
|
|
7327
7507
|
}
|
|
7328
|
-
call(function() {
|
|
7329
|
-
if (i < arr.length === false) {
|
|
7330
|
-
done(null, memo);
|
|
7331
|
-
} else {
|
|
7332
|
-
iter(memo, arr[i++], next);
|
|
7333
|
-
}
|
|
7334
|
-
}, 0);
|
|
7335
7508
|
}
|
|
7509
|
+
return fmt;
|
|
7336
7510
|
}
|
|
7337
7511
|
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
dest.push(src[i]);
|
|
7512
|
+
function shuffle(arr) {
|
|
7513
|
+
var tmp, i, j;
|
|
7514
|
+
for (i = arr.length - 1; i > 0; i--) {
|
|
7515
|
+
j = Math.floor(Math.random() * (i + 1));
|
|
7516
|
+
tmp = arr[i];
|
|
7517
|
+
arr[i] = arr[j];
|
|
7518
|
+
arr[j] = tmp;
|
|
7346
7519
|
}
|
|
7347
|
-
return dest;
|
|
7348
7520
|
}
|
|
7349
7521
|
|
|
7350
|
-
|
|
7351
|
-
|
|
7352
|
-
function difference(arr, other) {
|
|
7353
|
-
var index = arrayToIndex(other);
|
|
7354
|
-
return arr.filter(function(el) {
|
|
7355
|
-
return !Object.prototype.hasOwnProperty.call(index, el);
|
|
7356
|
-
});
|
|
7522
|
+
function pickOne(arr) {
|
|
7523
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
7357
7524
|
}
|
|
7358
7525
|
|
|
7359
|
-
//
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7526
|
+
// Sort an array of objects based on one or more properties.
|
|
7527
|
+
// Usage: sortOn(array, key1, asc?[, key2, asc? ...])
|
|
7528
|
+
//
|
|
7529
|
+
function sortOn(arr) {
|
|
7530
|
+
var comparators = [];
|
|
7531
|
+
for (var i=1; i<arguments.length; i+=2) {
|
|
7532
|
+
comparators.push(getKeyComparator(arguments[i], arguments[i+1]));
|
|
7533
|
+
}
|
|
7534
|
+
arr.sort(function(a, b) {
|
|
7535
|
+
var cmp = 0,
|
|
7536
|
+
i = 0,
|
|
7537
|
+
n = comparators.length;
|
|
7538
|
+
while (i < n && cmp === 0) {
|
|
7539
|
+
cmp = comparators[i](a, b);
|
|
7540
|
+
i++;
|
|
7541
|
+
}
|
|
7542
|
+
return cmp;
|
|
7363
7543
|
});
|
|
7544
|
+
return arr;
|
|
7364
7545
|
}
|
|
7365
7546
|
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7547
|
+
// Sort array of values that can be compared with < > operators (strings, numbers)
|
|
7548
|
+
// null, undefined and NaN are sorted to the end of the array
|
|
7549
|
+
// default order is ascending
|
|
7550
|
+
//
|
|
7551
|
+
function genericSort(arr, ascending) {
|
|
7552
|
+
var compare = getGenericComparator(ascending);
|
|
7553
|
+
Array.prototype.sort.call(arr, compare);
|
|
7554
|
+
return arr;
|
|
7373
7555
|
}
|
|
7374
7556
|
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
}
|
|
7380
|
-
else if (isArrayLike(container)) {
|
|
7381
|
-
return indexOf(container, item) != -1;
|
|
7382
|
-
}
|
|
7383
|
-
error("Expected Array or String argument");
|
|
7557
|
+
function getSortedIds(arr, asc) {
|
|
7558
|
+
var ids = range(arr.length);
|
|
7559
|
+
sortArrayIndex(ids, arr, asc);
|
|
7560
|
+
return ids;
|
|
7384
7561
|
}
|
|
7385
7562
|
|
|
7386
|
-
function
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7563
|
+
function sortArrayIndex(ids, arr, asc) {
|
|
7564
|
+
var compare = getGenericComparator(asc);
|
|
7565
|
+
ids.sort(function(i, j) {
|
|
7566
|
+
// added i, j comparison to guarantee that sort is stable
|
|
7567
|
+
var cmp = compare(arr[i], arr[j]);
|
|
7568
|
+
return cmp > 0 || cmp === 0 && i > j ? 1 : -1;
|
|
7569
|
+
});
|
|
7390
7570
|
}
|
|
7391
7571
|
|
|
7392
|
-
function
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7572
|
+
function reorderArray(arr, idxs) {
|
|
7573
|
+
var len = idxs.length;
|
|
7574
|
+
var arr2 = [];
|
|
7575
|
+
for (var i=0; i<len; i++) {
|
|
7576
|
+
var idx = idxs[i];
|
|
7577
|
+
if (idx < 0 || idx >= len) error$1("Out-of-bounds array idx");
|
|
7578
|
+
arr2[i] = arr[idx];
|
|
7579
|
+
}
|
|
7580
|
+
replaceArray(arr, arr2);
|
|
7396
7581
|
}
|
|
7397
7582
|
|
|
7398
|
-
function
|
|
7399
|
-
var
|
|
7400
|
-
return
|
|
7583
|
+
function getKeyComparator(key, asc) {
|
|
7584
|
+
var compare = getGenericComparator(asc);
|
|
7585
|
+
return function(a, b) {
|
|
7586
|
+
return compare(a[key], b[key]);
|
|
7587
|
+
};
|
|
7401
7588
|
}
|
|
7402
7589
|
|
|
7403
|
-
function
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7590
|
+
function getGenericComparator(asc) {
|
|
7591
|
+
asc = asc !== false && asc != 'descending'; // ascending is the default
|
|
7592
|
+
return function(a, b) {
|
|
7593
|
+
var retn = 0;
|
|
7594
|
+
if (b == null) {
|
|
7595
|
+
retn = a == null ? 0 : -1;
|
|
7596
|
+
} else if (a == null) {
|
|
7597
|
+
retn = 1;
|
|
7598
|
+
} else if (a < b) {
|
|
7599
|
+
retn = asc ? -1 : 1;
|
|
7600
|
+
} else if (a > b) {
|
|
7601
|
+
retn = asc ? 1 : -1;
|
|
7602
|
+
} else if (a !== a) {
|
|
7603
|
+
retn = 1;
|
|
7604
|
+
} else if (b !== b) {
|
|
7605
|
+
retn = -1;
|
|
7606
|
+
}
|
|
7607
|
+
return retn;
|
|
7608
|
+
};
|
|
7412
7609
|
}
|
|
7413
7610
|
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
values[i] = val;
|
|
7421
|
-
}
|
|
7422
|
-
}
|
|
7423
|
-
return values.length > 0 ? values : void 0;
|
|
7611
|
+
|
|
7612
|
+
// Generic in-place sort (null, NaN, undefined not handled)
|
|
7613
|
+
function quicksort(arr, asc) {
|
|
7614
|
+
quicksortPartition(arr, 0, arr.length-1);
|
|
7615
|
+
if (asc === false) Array.prototype.reverse.call(arr); // Works with typed arrays
|
|
7616
|
+
return arr;
|
|
7424
7617
|
}
|
|
7425
7618
|
|
|
7426
|
-
//
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7619
|
+
// Moved out of quicksort() (saw >100% speedup in Chrome with deep recursion)
|
|
7620
|
+
function quicksortPartition (a, lo, hi) {
|
|
7621
|
+
var i = lo,
|
|
7622
|
+
j = hi,
|
|
7623
|
+
pivot, tmp;
|
|
7624
|
+
while (i < hi) {
|
|
7625
|
+
pivot = a[lo + hi >> 1]; // avoid n^2 performance on sorted arrays
|
|
7626
|
+
while (i <= j) {
|
|
7627
|
+
while (a[i] < pivot) i++;
|
|
7628
|
+
while (a[j] > pivot) j--;
|
|
7629
|
+
if (i <= j) {
|
|
7630
|
+
tmp = a[i];
|
|
7631
|
+
a[i] = a[j];
|
|
7632
|
+
a[j] = tmp;
|
|
7633
|
+
i++;
|
|
7634
|
+
j--;
|
|
7635
|
+
}
|
|
7440
7636
|
}
|
|
7637
|
+
if (lo < j) quicksortPartition(a, lo, j);
|
|
7638
|
+
lo = i;
|
|
7639
|
+
j = hi;
|
|
7441
7640
|
}
|
|
7442
|
-
if (info) {
|
|
7443
|
-
info.nan = nan;
|
|
7444
|
-
}
|
|
7445
|
-
return tot;
|
|
7446
|
-
}
|
|
7447
|
-
|
|
7448
|
-
// Calculate min and max values of an array, ignoring NaN values
|
|
7449
|
-
function getArrayBounds(arr) {
|
|
7450
|
-
var min = Infinity,
|
|
7451
|
-
max = -Infinity,
|
|
7452
|
-
nan = 0, val;
|
|
7453
|
-
for (var i=0, len=arr.length; i<len; i++) {
|
|
7454
|
-
val = arr[i];
|
|
7455
|
-
if (val !== val) nan++;
|
|
7456
|
-
if (val < min) min = val;
|
|
7457
|
-
if (val > max) max = val;
|
|
7458
|
-
}
|
|
7459
|
-
return {
|
|
7460
|
-
min: min,
|
|
7461
|
-
max: max,
|
|
7462
|
-
nan: nan
|
|
7463
|
-
};
|
|
7464
7641
|
}
|
|
7465
7642
|
|
|
7466
|
-
// export function uniq(src) {
|
|
7467
|
-
// var index = {};
|
|
7468
|
-
// return src.reduce(function(memo, el) {
|
|
7469
|
-
// if (el in index === false) {
|
|
7470
|
-
// index[el] = true;
|
|
7471
|
-
// memo.push(el);
|
|
7472
|
-
// }
|
|
7473
|
-
// return memo;
|
|
7474
|
-
// }, []);
|
|
7475
|
-
// }
|
|
7476
7643
|
|
|
7477
|
-
function
|
|
7478
|
-
|
|
7479
|
-
var
|
|
7480
|
-
var
|
|
7481
|
-
|
|
7482
|
-
item = src[i];
|
|
7483
|
-
if (!index.has(item)) {
|
|
7484
|
-
arr.push(item);
|
|
7485
|
-
index.add(item);
|
|
7486
|
-
}
|
|
7644
|
+
function findRankByValue(arr, value) {
|
|
7645
|
+
if (isNaN(value)) return arr.length;
|
|
7646
|
+
var rank = 1;
|
|
7647
|
+
for (var i=0, n=arr.length; i<n; i++) {
|
|
7648
|
+
if (value > arr[i]) rank++;
|
|
7487
7649
|
}
|
|
7488
|
-
return
|
|
7650
|
+
return rank;
|
|
7489
7651
|
}
|
|
7490
7652
|
|
|
7491
|
-
function
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
});
|
|
7653
|
+
function findValueByPct(arr, pct) {
|
|
7654
|
+
var rank = Math.ceil((1-pct) * (arr.length));
|
|
7655
|
+
return findValueByRank(arr, rank);
|
|
7495
7656
|
}
|
|
7496
7657
|
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
}
|
|
7658
|
+
// See http://ndevilla.free.fr/median/median/src/wirth.c
|
|
7659
|
+
// Elements of @arr are reordered
|
|
7660
|
+
//
|
|
7661
|
+
function findValueByRank(arr, rank) {
|
|
7662
|
+
if (!arr.length || rank < 1 || rank > arr.length) error$1("[findValueByRank()] invalid input");
|
|
7503
7663
|
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7664
|
+
rank = clamp(rank | 0, 1, arr.length);
|
|
7665
|
+
var k = rank - 1, // conv. rank to array index
|
|
7666
|
+
n = arr.length,
|
|
7667
|
+
l = 0,
|
|
7668
|
+
m = n - 1,
|
|
7669
|
+
i, j, val, tmp;
|
|
7670
|
+
|
|
7671
|
+
while (l < m) {
|
|
7672
|
+
val = arr[k];
|
|
7673
|
+
i = l;
|
|
7674
|
+
j = m;
|
|
7675
|
+
do {
|
|
7676
|
+
while (arr[i] < val) {i++;}
|
|
7677
|
+
while (val < arr[j]) {j--;}
|
|
7678
|
+
if (i <= j) {
|
|
7679
|
+
tmp = arr[i];
|
|
7680
|
+
arr[i] = arr[j];
|
|
7681
|
+
arr[j] = tmp;
|
|
7682
|
+
i++;
|
|
7683
|
+
j--;
|
|
7684
|
+
}
|
|
7685
|
+
} while (i <= j);
|
|
7686
|
+
if (j < k) l = i;
|
|
7687
|
+
if (k < i) m = j;
|
|
7688
|
+
}
|
|
7689
|
+
return arr[k];
|
|
7509
7690
|
}
|
|
7510
7691
|
|
|
7511
|
-
function
|
|
7512
|
-
return arr
|
|
7513
|
-
var keyval = o[k];
|
|
7514
|
-
if (keyval in index) {
|
|
7515
|
-
index[keyval].push(o);
|
|
7516
|
-
} else {
|
|
7517
|
-
index[keyval] = [o];
|
|
7518
|
-
}
|
|
7519
|
-
return index;
|
|
7520
|
-
}, {});
|
|
7692
|
+
function findMedian(arr) {
|
|
7693
|
+
return findQuantile(arr, 0.5);
|
|
7521
7694
|
}
|
|
7522
7695
|
|
|
7523
|
-
function
|
|
7524
|
-
var
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7696
|
+
function findQuantile(arr, k) {
|
|
7697
|
+
var n = arr.length,
|
|
7698
|
+
i1 = Math.floor((n - 1) * k),
|
|
7699
|
+
i2 = Math.ceil((n - 1) * k);
|
|
7700
|
+
if (i1 < 0 || i2 >= n) return NaN;
|
|
7701
|
+
var v1 = findValueByRank(arr, i1 + 1);
|
|
7702
|
+
if (i1 == i2) return v1;
|
|
7703
|
+
var v2 = findValueByRank(arr, i2 + 1);
|
|
7704
|
+
// use linear interpolation
|
|
7705
|
+
var w1 = i2 / (n - 1) - k;
|
|
7706
|
+
var w2 = k - i1 / (n - 1);
|
|
7707
|
+
var v = (v1 * w1 + v2 * w2) * (n - 1);
|
|
7708
|
+
return v;
|
|
7529
7709
|
}
|
|
7530
7710
|
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7711
|
+
function mean(arr) {
|
|
7712
|
+
var count = 0,
|
|
7713
|
+
avg = NaN,
|
|
7714
|
+
val;
|
|
7715
|
+
for (var i=0, n=arr.length; i<n; i++) {
|
|
7716
|
+
val = arr[i];
|
|
7717
|
+
if (isNaN(val)) continue;
|
|
7718
|
+
avg = ++count == 1 ? val : val / count + (count - 1) / count * avg;
|
|
7538
7719
|
}
|
|
7720
|
+
return avg;
|
|
7539
7721
|
}
|
|
7540
7722
|
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7723
|
+
|
|
7724
|
+
/*
|
|
7725
|
+
A simplified version of printf formatting
|
|
7726
|
+
Format codes: %[flags][width][.precision]type
|
|
7727
|
+
|
|
7728
|
+
supported flags:
|
|
7729
|
+
+ add '+' before positive numbers
|
|
7730
|
+
0 left-pad with '0'
|
|
7731
|
+
' Add thousands separator
|
|
7732
|
+
width: 1 to many
|
|
7733
|
+
precision: .(1 to many)
|
|
7734
|
+
type:
|
|
7735
|
+
s string
|
|
7736
|
+
di integers
|
|
7737
|
+
f decimal numbers
|
|
7738
|
+
xX hexidecimal (unsigned)
|
|
7739
|
+
% literal '%'
|
|
7740
|
+
|
|
7741
|
+
Examples:
|
|
7742
|
+
code val formatted
|
|
7743
|
+
%+d 1 '+1'
|
|
7744
|
+
%4i 32 ' 32'
|
|
7745
|
+
%04i 32 '0032'
|
|
7746
|
+
%x 255 'ff'
|
|
7747
|
+
%.2f 0.125 '0.13'
|
|
7748
|
+
%'f 1000 '1,000'
|
|
7749
|
+
*/
|
|
7750
|
+
|
|
7751
|
+
// Usage: format(formatString, [values])
|
|
7752
|
+
// Tip: When reusing the same format many times, use formatter() for 5x - 10x better performance
|
|
7753
|
+
//
|
|
7754
|
+
function format(fmt) {
|
|
7755
|
+
var fn = formatter(fmt);
|
|
7756
|
+
var str = fn.apply(null, Array.prototype.slice.call(arguments, 1));
|
|
7757
|
+
return str;
|
|
7545
7758
|
}
|
|
7546
7759
|
|
|
7547
|
-
function
|
|
7548
|
-
|
|
7549
|
-
|
|
7760
|
+
function formatValue(val, matches) {
|
|
7761
|
+
var flags = matches[1];
|
|
7762
|
+
var padding = matches[2];
|
|
7763
|
+
var decimals = matches[3] ? parseInt(matches[3].substr(1)) : void 0;
|
|
7764
|
+
var type = matches[4];
|
|
7765
|
+
var isString = type == 's',
|
|
7766
|
+
isHex = type == 'x' || type == 'X',
|
|
7767
|
+
// isInt = type == 'd' || type == 'i',
|
|
7768
|
+
// isFloat = type == 'f',
|
|
7769
|
+
isNumber = !isString;
|
|
7770
|
+
|
|
7771
|
+
var sign = "",
|
|
7772
|
+
padDigits = 0,
|
|
7773
|
+
isZero = false,
|
|
7774
|
+
isNeg = false;
|
|
7775
|
+
|
|
7776
|
+
var str, padChar, padStr;
|
|
7777
|
+
if (isString) {
|
|
7778
|
+
str = String(val);
|
|
7779
|
+
}
|
|
7780
|
+
else if (isHex) {
|
|
7781
|
+
str = val.toString(16);
|
|
7782
|
+
if (type == 'X')
|
|
7783
|
+
str = str.toUpperCase();
|
|
7784
|
+
}
|
|
7785
|
+
else if (isNumber) {
|
|
7786
|
+
// str = formatNumberForDisplay(val, isInt ? 0 : decimals);
|
|
7787
|
+
str = numToStr(val, decimals);
|
|
7788
|
+
if (str[0] == '-') {
|
|
7789
|
+
isNeg = true;
|
|
7790
|
+
str = str.substr(1);
|
|
7791
|
+
}
|
|
7792
|
+
isZero = parseFloat(str) == 0;
|
|
7793
|
+
if (flags.indexOf("'") != -1 || flags.indexOf(',') != -1) {
|
|
7794
|
+
str = addThousandsSep(str);
|
|
7795
|
+
}
|
|
7796
|
+
if (!isZero) { // BUG: sign is added when num rounds to 0
|
|
7797
|
+
if (isNeg) {
|
|
7798
|
+
sign = "\u2212"; // U+2212
|
|
7799
|
+
} else if (flags.indexOf('+') != -1) {
|
|
7800
|
+
sign = '+';
|
|
7801
|
+
}
|
|
7802
|
+
}
|
|
7550
7803
|
}
|
|
7551
|
-
return arr;
|
|
7552
|
-
}
|
|
7553
7804
|
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7805
|
+
if (padding) {
|
|
7806
|
+
var strLen = str.length + sign.length;
|
|
7807
|
+
var minWidth = parseInt(padding, 10);
|
|
7808
|
+
if (strLen < minWidth) {
|
|
7809
|
+
padDigits = minWidth - strLen;
|
|
7810
|
+
padChar = flags.indexOf('0') == -1 ? ' ' : '0';
|
|
7811
|
+
padStr = repeatString(padChar, padDigits);
|
|
7812
|
+
}
|
|
7558
7813
|
}
|
|
7559
|
-
}
|
|
7560
7814
|
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
str
|
|
7815
|
+
if (padDigits == 0) {
|
|
7816
|
+
str = sign + str;
|
|
7817
|
+
} else if (padChar == '0') {
|
|
7818
|
+
str = sign + padStr + str;
|
|
7819
|
+
} else {
|
|
7820
|
+
str = padStr + sign + str;
|
|
7821
|
+
}
|
|
7565
7822
|
return str;
|
|
7566
7823
|
}
|
|
7567
7824
|
|
|
7568
|
-
function
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7825
|
+
// Get a function for interpolating formatted values into a string.
|
|
7826
|
+
function formatter(fmt) {
|
|
7827
|
+
var codeRxp = /%([',+0]*)([1-9]?)((?:\.[1-9])?)([sdifxX%])/g;
|
|
7828
|
+
var literals = [],
|
|
7829
|
+
formatCodes = [],
|
|
7830
|
+
startIdx = 0,
|
|
7831
|
+
prefix = "",
|
|
7832
|
+
matches = codeRxp.exec(fmt),
|
|
7833
|
+
literal;
|
|
7575
7834
|
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7835
|
+
while (matches) {
|
|
7836
|
+
literal = fmt.substring(startIdx, codeRxp.lastIndex - matches[0].length);
|
|
7837
|
+
if (matches[0] == '%%') {
|
|
7838
|
+
prefix += literal + '%';
|
|
7839
|
+
} else {
|
|
7840
|
+
literals.push(prefix + literal);
|
|
7841
|
+
prefix = '';
|
|
7842
|
+
formatCodes.push(matches);
|
|
7843
|
+
}
|
|
7844
|
+
startIdx = codeRxp.lastIndex;
|
|
7845
|
+
matches = codeRxp.exec(fmt);
|
|
7846
|
+
}
|
|
7847
|
+
literals.push(prefix + fmt.substr(startIdx));
|
|
7579
7848
|
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7849
|
+
return function() {
|
|
7850
|
+
var str = literals[0],
|
|
7851
|
+
n = arguments.length;
|
|
7852
|
+
if (n != formatCodes.length) {
|
|
7853
|
+
error$1("[format()] Data does not match format string; format:", fmt, "data:", arguments);
|
|
7854
|
+
}
|
|
7855
|
+
for (var i=0; i<n; i++) {
|
|
7856
|
+
str += formatValue(arguments[i], formatCodes[i]) + literals[i+1];
|
|
7857
|
+
}
|
|
7858
|
+
return str;
|
|
7859
|
+
};
|
|
7584
7860
|
}
|
|
7585
7861
|
|
|
7586
|
-
function
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7862
|
+
function wildcardToRegExp(name) {
|
|
7863
|
+
var rxp = name.split('*').map(function(str) {
|
|
7864
|
+
return regexEscape(str);
|
|
7865
|
+
}).join('.*');
|
|
7866
|
+
return new RegExp('^' + rxp + '$');
|
|
7590
7867
|
}
|
|
7591
7868
|
|
|
7592
|
-
function
|
|
7593
|
-
|
|
7869
|
+
function createBuffer(arg, arg2) {
|
|
7870
|
+
if (isInteger(arg)) {
|
|
7871
|
+
return B.allocUnsafe ? B.allocUnsafe(arg) : new B(arg);
|
|
7872
|
+
} else {
|
|
7873
|
+
// check allocUnsafe to make sure Buffer.from() will accept strings (it didn't before Node v5.10)
|
|
7874
|
+
return B.from && B.allocUnsafe ? B.from(arg, arg2) : new B(arg, arg2);
|
|
7875
|
+
}
|
|
7594
7876
|
}
|
|
7595
7877
|
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
return
|
|
7878
|
+
function toBuffer(src) {
|
|
7879
|
+
if (src instanceof B) return src;
|
|
7880
|
+
if (src instanceof ArrayBuffer) return B.from(src);
|
|
7881
|
+
if (src instanceof Uint8Array) {
|
|
7882
|
+
return B.from(src.buffer, src.byteOffset, src.byteLength);
|
|
7883
|
+
}
|
|
7884
|
+
error$1('Unexpected argument type');
|
|
7599
7885
|
}
|
|
7600
7886
|
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7887
|
+
function expandoBuffer(constructor, rate) {
|
|
7888
|
+
var capacity = 0,
|
|
7889
|
+
k = rate >= 1 ? rate : 1.2,
|
|
7890
|
+
buf;
|
|
7891
|
+
return function(size) {
|
|
7892
|
+
if (size > capacity) {
|
|
7893
|
+
capacity = Math.ceil(size * k);
|
|
7894
|
+
buf = constructor ? new constructor(capacity) : createBuffer(capacity);
|
|
7895
|
+
}
|
|
7896
|
+
return buf;
|
|
7897
|
+
};
|
|
7604
7898
|
}
|
|
7605
7899
|
|
|
7606
|
-
function
|
|
7607
|
-
var
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7900
|
+
function copyElements(src, i, dest, j, n, rev) {
|
|
7901
|
+
var same = src == dest || src.buffer && src.buffer == dest.buffer;
|
|
7902
|
+
var inc = 1,
|
|
7903
|
+
offs = 0,
|
|
7904
|
+
k;
|
|
7905
|
+
if (rev) {
|
|
7906
|
+
if (same) error$1('copy error');
|
|
7907
|
+
inc = -1;
|
|
7908
|
+
offs = n - 1;
|
|
7909
|
+
}
|
|
7910
|
+
if (same && j > i) {
|
|
7911
|
+
for (k=n-1; k>=0; k--) {
|
|
7912
|
+
dest[j + k] = src[i + k];
|
|
7913
|
+
}
|
|
7914
|
+
} else {
|
|
7915
|
+
for (k=0; k<n; k++, offs += inc) {
|
|
7916
|
+
dest[k + j] = src[i + offs];
|
|
7917
|
+
}
|
|
7616
7918
|
}
|
|
7617
|
-
return str.substring(0, end) + fmt;
|
|
7618
|
-
}
|
|
7619
|
-
|
|
7620
|
-
function numToStr(num, decimals) {
|
|
7621
|
-
return decimals >= 0 ? num.toFixed(decimals) : String(num);
|
|
7622
7919
|
}
|
|
7623
7920
|
|
|
7624
|
-
function
|
|
7625
|
-
|
|
7921
|
+
function extendBuffer(src, newLen, copyLen) {
|
|
7922
|
+
var len = Math.max(src.length, newLen);
|
|
7923
|
+
var n = copyLen || src.length;
|
|
7924
|
+
var dest = new src.constructor(len);
|
|
7925
|
+
copyElements(src, 0, dest, 0, n);
|
|
7926
|
+
return dest;
|
|
7626
7927
|
}
|
|
7627
7928
|
|
|
7628
|
-
function
|
|
7629
|
-
var
|
|
7630
|
-
|
|
7929
|
+
function mergeNames(name1, name2) {
|
|
7930
|
+
var merged;
|
|
7931
|
+
if (name1 && name2) {
|
|
7932
|
+
merged = findStringPrefix(name1, name2).replace(/[-_]$/, '');
|
|
7933
|
+
}
|
|
7934
|
+
return merged || '';
|
|
7631
7935
|
}
|
|
7632
7936
|
|
|
7633
|
-
function
|
|
7634
|
-
var
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
} else {
|
|
7638
|
-
fmt = numToStr(num, decimals);
|
|
7639
|
-
fmt = addThousandsSep(fmt);
|
|
7640
|
-
if (showPos && parseFloat(fmt) > 0) {
|
|
7641
|
-
fmt = "+" + fmt;
|
|
7642
|
-
}
|
|
7937
|
+
function findStringPrefix(a, b) {
|
|
7938
|
+
var i = 0;
|
|
7939
|
+
for (var n=a.length; i<n; i++) {
|
|
7940
|
+
if (a[i] !== b[i]) break;
|
|
7643
7941
|
}
|
|
7644
|
-
return
|
|
7942
|
+
return a.substr(0, i);
|
|
7645
7943
|
}
|
|
7646
7944
|
|
|
7647
|
-
function
|
|
7648
|
-
var
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
tmp = arr[i];
|
|
7652
|
-
arr[i] = arr[j];
|
|
7653
|
-
arr[j] = tmp;
|
|
7945
|
+
function formatVersionedName(name, i) {
|
|
7946
|
+
var suffix = String(i);
|
|
7947
|
+
if (/[0-9]$/.test(name)) {
|
|
7948
|
+
suffix = '-' + suffix;
|
|
7654
7949
|
}
|
|
7950
|
+
return name + suffix;
|
|
7655
7951
|
}
|
|
7656
7952
|
|
|
7657
|
-
function
|
|
7658
|
-
|
|
7659
|
-
|
|
7953
|
+
function uniqifyNames(names, formatter) {
|
|
7954
|
+
var counts = countValues(names),
|
|
7955
|
+
format = formatter || formatVersionedName,
|
|
7956
|
+
names2 = [];
|
|
7660
7957
|
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
arr.sort(function(a, b) {
|
|
7670
|
-
var cmp = 0,
|
|
7671
|
-
i = 0,
|
|
7672
|
-
n = comparators.length;
|
|
7673
|
-
while (i < n && cmp === 0) {
|
|
7674
|
-
cmp = comparators[i](a, b);
|
|
7958
|
+
names.forEach(function(name) {
|
|
7959
|
+
var i = 0,
|
|
7960
|
+
candidate = name,
|
|
7961
|
+
versionedName;
|
|
7962
|
+
while (
|
|
7963
|
+
names2.indexOf(candidate) > -1 || // candidate name has already been used
|
|
7964
|
+
candidate == name && counts[candidate] > 1 || // duplicate unversioned names
|
|
7965
|
+
candidate != name && counts[candidate] > 0) { // versioned name is a preexisting name
|
|
7675
7966
|
i++;
|
|
7967
|
+
versionedName = format(name, i);
|
|
7968
|
+
if (!versionedName || versionedName == candidate) {
|
|
7969
|
+
throw new Error("Naming error"); // catch buggy versioning function
|
|
7970
|
+
}
|
|
7971
|
+
candidate = versionedName;
|
|
7676
7972
|
}
|
|
7677
|
-
|
|
7973
|
+
names2.push(candidate);
|
|
7678
7974
|
});
|
|
7679
|
-
return
|
|
7975
|
+
return names2;
|
|
7680
7976
|
}
|
|
7681
7977
|
|
|
7682
|
-
// Sort array of values that can be compared with < > operators (strings, numbers)
|
|
7683
|
-
// null, undefined and NaN are sorted to the end of the array
|
|
7684
|
-
// default order is ascending
|
|
7685
|
-
//
|
|
7686
|
-
function genericSort(arr, ascending) {
|
|
7687
|
-
var compare = getGenericComparator(ascending);
|
|
7688
|
-
Array.prototype.sort.call(arr, compare);
|
|
7689
|
-
return arr;
|
|
7690
|
-
}
|
|
7691
7978
|
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
return ids;
|
|
7979
|
+
// Assume: @raw is string, undefined or null
|
|
7980
|
+
function parseString(raw) {
|
|
7981
|
+
return raw ? raw : "";
|
|
7696
7982
|
}
|
|
7697
7983
|
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
});
|
|
7984
|
+
// Assume: @raw is string, undefined or null
|
|
7985
|
+
// Use null instead of NaN for unparsable values
|
|
7986
|
+
// (in part because if NaN is used, empty strings get converted to "NaN"
|
|
7987
|
+
// when re-exported).
|
|
7988
|
+
function parseNumber(raw) {
|
|
7989
|
+
return parseToNum(raw, cleanNumericString);
|
|
7705
7990
|
}
|
|
7706
7991
|
|
|
7707
|
-
function
|
|
7708
|
-
|
|
7709
|
-
var arr2 = [];
|
|
7710
|
-
for (var i=0; i<len; i++) {
|
|
7711
|
-
var idx = idxs[i];
|
|
7712
|
-
if (idx < 0 || idx >= len) error("Out-of-bounds array idx");
|
|
7713
|
-
arr2[i] = arr[idx];
|
|
7714
|
-
}
|
|
7715
|
-
replaceArray(arr, arr2);
|
|
7992
|
+
function parseIntlNumber(raw) {
|
|
7993
|
+
return parseToNum(raw, convertIntlNumString);
|
|
7716
7994
|
}
|
|
7717
7995
|
|
|
7718
|
-
function
|
|
7719
|
-
var
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
};
|
|
7996
|
+
function parseToNum(raw, clean) {
|
|
7997
|
+
var str = String(raw).trim();
|
|
7998
|
+
var parsed = str ? Number(clean(str)) : NaN;
|
|
7999
|
+
return isNaN(parsed) ? null : parsed;
|
|
7723
8000
|
}
|
|
7724
8001
|
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
return
|
|
7728
|
-
var retn = 0;
|
|
7729
|
-
if (b == null) {
|
|
7730
|
-
retn = a == null ? 0 : -1;
|
|
7731
|
-
} else if (a == null) {
|
|
7732
|
-
retn = 1;
|
|
7733
|
-
} else if (a < b) {
|
|
7734
|
-
retn = asc ? -1 : 1;
|
|
7735
|
-
} else if (a > b) {
|
|
7736
|
-
retn = asc ? 1 : -1;
|
|
7737
|
-
} else if (a !== a) {
|
|
7738
|
-
retn = 1;
|
|
7739
|
-
} else if (b !== b) {
|
|
7740
|
-
retn = -1;
|
|
7741
|
-
}
|
|
7742
|
-
return retn;
|
|
7743
|
-
};
|
|
8002
|
+
// Remove comma separators from strings
|
|
8003
|
+
function cleanNumericString(str) {
|
|
8004
|
+
return (str.indexOf(',') > 0) ? str.replace(/,([0-9]{3})/g, '$1') : str;
|
|
7744
8005
|
}
|
|
7745
8006
|
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
quicksortPartition(arr, 0, arr.length-1);
|
|
7750
|
-
if (asc === false) Array.prototype.reverse.call(arr); // Works with typed arrays
|
|
7751
|
-
return arr;
|
|
8007
|
+
function convertIntlNumString(str) {
|
|
8008
|
+
str = str.replace(/[ .]([0-9]{3})/g, '$1');
|
|
8009
|
+
return str.replace(',', '.');
|
|
7752
8010
|
}
|
|
7753
8011
|
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
j--;
|
|
7770
|
-
}
|
|
8012
|
+
function trimQuotes(str) {
|
|
8013
|
+
var len = str.length, first, last;
|
|
8014
|
+
if (len >= 2) {
|
|
8015
|
+
first = str.charAt(0);
|
|
8016
|
+
last = str.charAt(len-1);
|
|
8017
|
+
// if (first == '"' && last == '"' && !str.includes('","') ||
|
|
8018
|
+
// first == "'" && last == "'" && !str.includes("','")) {
|
|
8019
|
+
// don't strip if there are unescaped quotes
|
|
8020
|
+
// e.g. expressions that start and end with quotes
|
|
8021
|
+
// e.g. comma-separated list of quoted values
|
|
8022
|
+
if (first == '"' && last == '"' && !/[^\\]"./.test(str) ||
|
|
8023
|
+
first == "'" && last == "'" && !/[^\\]'./.test(str)) {
|
|
8024
|
+
str = str.substr(1, len-2);
|
|
8025
|
+
// remove string escapes
|
|
8026
|
+
str = str.replace(first == '"' ? /\\(?=")/g : /\\(?=')/g, '');
|
|
7771
8027
|
}
|
|
7772
|
-
if (lo < j) quicksortPartition(a, lo, j);
|
|
7773
|
-
lo = i;
|
|
7774
|
-
j = hi;
|
|
7775
|
-
}
|
|
7776
|
-
}
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
function findRankByValue(arr, value) {
|
|
7780
|
-
if (isNaN(value)) return arr.length;
|
|
7781
|
-
var rank = 1;
|
|
7782
|
-
for (var i=0, n=arr.length; i<n; i++) {
|
|
7783
|
-
if (value > arr[i]) rank++;
|
|
7784
8028
|
}
|
|
7785
|
-
return
|
|
8029
|
+
return str;
|
|
7786
8030
|
}
|
|
7787
8031
|
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
8032
|
+
// Default export so consumers can do `import utils from './mapshaper-utils'`
|
|
8033
|
+
// and call `utils.isObject(x)` etc. Listing the names explicitly (instead
|
|
8034
|
+
// of using `import * as utils from './mapshaper-utils'`) avoids a
|
|
8035
|
+
// self-import and the resulting Rollup circular-dependency warning.
|
|
8036
|
+
var utils = {
|
|
8037
|
+
addThousandsSep, addslashes, arrayToIndex,
|
|
8038
|
+
clamp, cleanNumericString, contains, copyElements, countValues, createBuffer,
|
|
8039
|
+
defaults, difference,
|
|
8040
|
+
endsWith, every, expandoBuffer, extend, extendBuffer,
|
|
8041
|
+
find, findMedian, findQuantile, findRankByValue, findStringPrefix,
|
|
8042
|
+
findValueByPct, findValueByRank, forEach, forEachProperty, format,
|
|
8043
|
+
formatDateISO, formatIntlNumber, formatNumber, formatNumberForDisplay,
|
|
8044
|
+
formatVersionedName, formatter,
|
|
8045
|
+
genericSort, getArrayBounds, getGenericComparator, getKeyComparator,
|
|
8046
|
+
getSortedIds, getUniqueName, groupBy,
|
|
8047
|
+
htmlEscape,
|
|
8048
|
+
indexOf, indexOn, inherit, initializeArray, intersection,
|
|
8049
|
+
isArray, isArrayLike, isBoolean, isDate, isEven, isFiniteNumber, isFunction,
|
|
8050
|
+
isInteger, isNonNegNumber, isNumber, isObject, isOdd, isPromise, isString,
|
|
8051
|
+
isValidNumber,
|
|
8052
|
+
lpad, ltrim,
|
|
8053
|
+
mean, merge, mergeNames,
|
|
8054
|
+
numToStr,
|
|
8055
|
+
parseIntlNumber, parseNumber, parseString, pickOne, pluck,
|
|
8056
|
+
pluralSuffix, promisify,
|
|
8057
|
+
quicksort, quicksortPartition,
|
|
8058
|
+
range, reduceAsync, regexEscape, reorderArray, repeat, repeatString,
|
|
8059
|
+
replaceArray, rpad, rtrim,
|
|
8060
|
+
shuffle, some, sortArrayIndex, sortOn, splitLines, sum,
|
|
8061
|
+
toArray, toBuffer, trim, trimQuotes,
|
|
8062
|
+
uniq, uniqifyNames,
|
|
8063
|
+
wildcardToRegExp
|
|
8064
|
+
};
|
|
7792
8065
|
|
|
7793
|
-
//
|
|
7794
|
-
//
|
|
8066
|
+
// This module provides a way for multiple jobs to run together asynchronously
|
|
8067
|
+
// while keeping job-level context variables (like "defs") separate.
|
|
7795
8068
|
//
|
|
7796
|
-
|
|
7797
|
-
|
|
8069
|
+
// We deliberately do NOT import from mapshaper-logging here -- the logging
|
|
8070
|
+
// module imports from this one, and avoiding the back-edge keeps the
|
|
8071
|
+
// foundational dependency graph acyclic. (The single error path below is
|
|
8072
|
+
// an internal-bug guard, not a user-facing message, so a plain Error is
|
|
8073
|
+
// adequate.)
|
|
7798
8074
|
|
|
7799
|
-
|
|
7800
|
-
var k = rank - 1, // conv. rank to array index
|
|
7801
|
-
n = arr.length,
|
|
7802
|
-
l = 0,
|
|
7803
|
-
m = n - 1,
|
|
7804
|
-
i, j, val, tmp;
|
|
8075
|
+
var stash = {};
|
|
7805
8076
|
|
|
7806
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
j = m;
|
|
7810
|
-
do {
|
|
7811
|
-
while (arr[i] < val) {i++;}
|
|
7812
|
-
while (val < arr[j]) {j--;}
|
|
7813
|
-
if (i <= j) {
|
|
7814
|
-
tmp = arr[i];
|
|
7815
|
-
arr[i] = arr[j];
|
|
7816
|
-
arr[j] = tmp;
|
|
7817
|
-
i++;
|
|
7818
|
-
j--;
|
|
7819
|
-
}
|
|
7820
|
-
} while (i <= j);
|
|
7821
|
-
if (j < k) l = i;
|
|
7822
|
-
if (k < i) m = j;
|
|
8077
|
+
function stashVar(key, val) {
|
|
8078
|
+
if (key in stash) {
|
|
8079
|
+
throw new Error('Tried to replace a stashed variable: ' + key);
|
|
7823
8080
|
}
|
|
7824
|
-
|
|
8081
|
+
stash[key] = val;
|
|
7825
8082
|
}
|
|
7826
8083
|
|
|
7827
|
-
function
|
|
7828
|
-
|
|
8084
|
+
function getStashedVar(key) {
|
|
8085
|
+
if (key in stash === false) {
|
|
8086
|
+
return undefined; // to support running commands in tests
|
|
8087
|
+
// error('Tried to read a nonexistent variable from the stash:', key);
|
|
8088
|
+
}
|
|
8089
|
+
return stash[key];
|
|
7829
8090
|
}
|
|
7830
8091
|
|
|
7831
|
-
function
|
|
7832
|
-
|
|
7833
|
-
i1 = Math.floor((n - 1) * k),
|
|
7834
|
-
i2 = Math.ceil((n - 1) * k);
|
|
7835
|
-
if (i1 < 0 || i2 >= n) return NaN;
|
|
7836
|
-
var v1 = findValueByRank(arr, i1 + 1);
|
|
7837
|
-
if (i1 == i2) return v1;
|
|
7838
|
-
var v2 = findValueByRank(arr, i2 + 1);
|
|
7839
|
-
// use linear interpolation
|
|
7840
|
-
var w1 = i2 / (n - 1) - k;
|
|
7841
|
-
var w2 = k - i1 / (n - 1);
|
|
7842
|
-
var v = (v1 * w1 + v2 * w2) * (n - 1);
|
|
7843
|
-
return v;
|
|
8092
|
+
function clearStash() {
|
|
8093
|
+
stash = {};
|
|
7844
8094
|
}
|
|
7845
8095
|
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
8096
|
+
var LOGGING = false;
|
|
8097
|
+
var STDOUT = false; // use stdout for status messages
|
|
8098
|
+
var _error, _stop, _message, _warn;
|
|
8099
|
+
|
|
8100
|
+
var _interrupt = function() {
|
|
8101
|
+
throw new NonFatalError(formatLogArgs(arguments));
|
|
8102
|
+
};
|
|
8103
|
+
|
|
8104
|
+
var onceMessages = [];
|
|
8105
|
+
|
|
8106
|
+
setLoggingForCLI();
|
|
8107
|
+
|
|
8108
|
+
function getLoggingSetter() {
|
|
8109
|
+
var e = _error, s = _stop, m = _message, w = _warn;
|
|
8110
|
+
return function() {
|
|
8111
|
+
setLoggingFunctions(m, e, s, w);
|
|
8112
|
+
};
|
|
7856
8113
|
}
|
|
7857
8114
|
|
|
8115
|
+
function setLoggingForCLI() {
|
|
8116
|
+
function stop() {
|
|
8117
|
+
throw new UserError(formatLogArgs(arguments));
|
|
8118
|
+
}
|
|
7858
8119
|
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
8120
|
+
function error() {
|
|
8121
|
+
var msg = utils.toArray(arguments).join(' ');
|
|
8122
|
+
throw new Error(msg);
|
|
8123
|
+
}
|
|
7862
8124
|
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
' Add thousands separator
|
|
7867
|
-
width: 1 to many
|
|
7868
|
-
precision: .(1 to many)
|
|
7869
|
-
type:
|
|
7870
|
-
s string
|
|
7871
|
-
di integers
|
|
7872
|
-
f decimal numbers
|
|
7873
|
-
xX hexidecimal (unsigned)
|
|
7874
|
-
% literal '%'
|
|
8125
|
+
function message() {
|
|
8126
|
+
logArgs(arguments);
|
|
8127
|
+
}
|
|
7875
8128
|
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
%+d 1 '+1'
|
|
7879
|
-
%4i 32 ' 32'
|
|
7880
|
-
%04i 32 '0032'
|
|
7881
|
-
%x 255 'ff'
|
|
7882
|
-
%.2f 0.125 '0.13'
|
|
7883
|
-
%'f 1000 '1,000'
|
|
7884
|
-
*/
|
|
8129
|
+
// CLI warning is just a message (GUI behaves differently)
|
|
8130
|
+
var warn = message;
|
|
7885
8131
|
|
|
7886
|
-
|
|
7887
|
-
// Tip: When reusing the same format many times, use formatter() for 5x - 10x better performance
|
|
7888
|
-
//
|
|
7889
|
-
function format(fmt) {
|
|
7890
|
-
var fn = formatter(fmt);
|
|
7891
|
-
var str = fn.apply(null, Array.prototype.slice.call(arguments, 1));
|
|
7892
|
-
return str;
|
|
8132
|
+
setLoggingFunctions(message, error, stop, warn);
|
|
7893
8133
|
}
|
|
7894
8134
|
|
|
7895
|
-
function
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
var decimals = matches[3] ? parseInt(matches[3].substr(1)) : void 0;
|
|
7899
|
-
var type = matches[4];
|
|
7900
|
-
var isString = type == 's',
|
|
7901
|
-
isHex = type == 'x' || type == 'X',
|
|
7902
|
-
// isInt = type == 'd' || type == 'i',
|
|
7903
|
-
// isFloat = type == 'f',
|
|
7904
|
-
isNumber = !isString;
|
|
8135
|
+
function enableLogging() {
|
|
8136
|
+
LOGGING = true;
|
|
8137
|
+
}
|
|
7905
8138
|
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
isNeg = false;
|
|
8139
|
+
function disableLogging() {
|
|
8140
|
+
LOGGING = false;
|
|
8141
|
+
}
|
|
7910
8142
|
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
}
|
|
7915
|
-
else if (isHex) {
|
|
7916
|
-
str = val.toString(16);
|
|
7917
|
-
if (type == 'X')
|
|
7918
|
-
str = str.toUpperCase();
|
|
7919
|
-
}
|
|
7920
|
-
else if (isNumber) {
|
|
7921
|
-
// str = formatNumberForDisplay(val, isInt ? 0 : decimals);
|
|
7922
|
-
str = numToStr(val, decimals);
|
|
7923
|
-
if (str[0] == '-') {
|
|
7924
|
-
isNeg = true;
|
|
7925
|
-
str = str.substr(1);
|
|
7926
|
-
}
|
|
7927
|
-
isZero = parseFloat(str) == 0;
|
|
7928
|
-
if (flags.indexOf("'") != -1 || flags.indexOf(',') != -1) {
|
|
7929
|
-
str = addThousandsSep(str);
|
|
7930
|
-
}
|
|
7931
|
-
if (!isZero) { // BUG: sign is added when num rounds to 0
|
|
7932
|
-
if (isNeg) {
|
|
7933
|
-
sign = "\u2212"; // U+2212
|
|
7934
|
-
} else if (flags.indexOf('+') != -1) {
|
|
7935
|
-
sign = '+';
|
|
7936
|
-
}
|
|
7937
|
-
}
|
|
7938
|
-
}
|
|
8143
|
+
function loggingEnabled() {
|
|
8144
|
+
return !!LOGGING;
|
|
8145
|
+
}
|
|
7939
8146
|
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
padDigits = minWidth - strLen;
|
|
7945
|
-
padChar = flags.indexOf('0') == -1 ? ' ' : '0';
|
|
7946
|
-
padStr = repeatString(padChar, padDigits);
|
|
7947
|
-
}
|
|
7948
|
-
}
|
|
8147
|
+
// Handle an unexpected condition (internal error)
|
|
8148
|
+
function error() {
|
|
8149
|
+
_error.apply(null, utils.toArray(arguments));
|
|
8150
|
+
}
|
|
7949
8151
|
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
} else {
|
|
7955
|
-
str = padStr + sign + str;
|
|
7956
|
-
}
|
|
7957
|
-
return str;
|
|
8152
|
+
// Handle an error caused by invalid input or misuse of API
|
|
8153
|
+
function stop() {
|
|
8154
|
+
// _stop.apply(null, utils.toArray(arguments));
|
|
8155
|
+
_stop.apply(null, messageArgs(arguments));
|
|
7958
8156
|
}
|
|
7959
8157
|
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
var literals = [],
|
|
7964
|
-
formatCodes = [],
|
|
7965
|
-
startIdx = 0,
|
|
7966
|
-
prefix = "",
|
|
7967
|
-
matches = codeRxp.exec(fmt),
|
|
7968
|
-
literal;
|
|
8158
|
+
function interrupt() {
|
|
8159
|
+
_interrupt.apply(null, utils.toArray(arguments));
|
|
8160
|
+
}
|
|
7969
8161
|
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
} else {
|
|
7975
|
-
literals.push(prefix + literal);
|
|
7976
|
-
prefix = '';
|
|
7977
|
-
formatCodes.push(matches);
|
|
7978
|
-
}
|
|
7979
|
-
startIdx = codeRxp.lastIndex;
|
|
7980
|
-
matches = codeRxp.exec(fmt);
|
|
7981
|
-
}
|
|
7982
|
-
literals.push(prefix + fmt.substr(startIdx));
|
|
8162
|
+
// Print a status message
|
|
8163
|
+
function message() {
|
|
8164
|
+
_message.apply(null, messageArgs(arguments));
|
|
8165
|
+
}
|
|
7983
8166
|
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
n = arguments.length;
|
|
7987
|
-
if (n != formatCodes.length) {
|
|
7988
|
-
error("[format()] Data does not match format string; format:", fmt, "data:", arguments);
|
|
7989
|
-
}
|
|
7990
|
-
for (var i=0; i<n; i++) {
|
|
7991
|
-
str += formatValue(arguments[i], formatCodes[i]) + literals[i+1];
|
|
7992
|
-
}
|
|
7993
|
-
return str;
|
|
7994
|
-
};
|
|
8167
|
+
function warn() {
|
|
8168
|
+
_warn.apply(null, messageArgs(arguments));
|
|
7995
8169
|
}
|
|
7996
8170
|
|
|
7997
|
-
function
|
|
7998
|
-
var
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8171
|
+
function warnOnce() {
|
|
8172
|
+
var str = formatLogArgs(arguments);
|
|
8173
|
+
if (onceMessages.includes(str)) return;
|
|
8174
|
+
onceMessages.push(str);
|
|
8175
|
+
_warn.apply(null, messageArgs(arguments));
|
|
8002
8176
|
}
|
|
8003
8177
|
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
}
|
|
8178
|
+
// A way for the GUI to replace the CLI logging functions
|
|
8179
|
+
function setLoggingFunctions(message, error, stop, warn) {
|
|
8180
|
+
_message = message;
|
|
8181
|
+
_error = error;
|
|
8182
|
+
_stop = stop;
|
|
8183
|
+
_warn = warn;
|
|
8011
8184
|
}
|
|
8012
8185
|
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8186
|
+
// get detailed error information from error stack (if available)
|
|
8187
|
+
// Example stack string (Node.js):
|
|
8188
|
+
/*
|
|
8189
|
+
/Users/someuser/somescript.js:226
|
|
8190
|
+
opacity: Math.round(weight * 5 / 5 // 0.2 0.4 0.6 etc
|
|
8191
|
+
^
|
|
8192
|
+
|
|
8193
|
+
SyntaxError: missing ) after argument list
|
|
8194
|
+
at internalCompileFunction (node:internal/vm:73:18)
|
|
8195
|
+
at wrapSafe (node:internal/modules/cjs/loader:1149:20)
|
|
8196
|
+
at Module._compile (node:internal/modules/cjs/loader:1190:27)
|
|
8197
|
+
...
|
|
8198
|
+
*/
|
|
8199
|
+
function getErrorDetail(e) {
|
|
8200
|
+
var parts = (typeof e.stack == 'string') ? e.stack.split(/\n\s*\n/) : [];
|
|
8201
|
+
if (parts.length > 1 || true) {
|
|
8202
|
+
return '\nError details:\n' + parts[0];
|
|
8018
8203
|
}
|
|
8019
|
-
|
|
8204
|
+
return '';
|
|
8020
8205
|
}
|
|
8021
8206
|
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
capacity = Math.ceil(size * k);
|
|
8029
|
-
buf = constructor ? new constructor(capacity) : createBuffer(capacity);
|
|
8030
|
-
}
|
|
8031
|
-
return buf;
|
|
8032
|
-
};
|
|
8207
|
+
// print a message to stdout
|
|
8208
|
+
function print() {
|
|
8209
|
+
STDOUT = true; // tell logArgs() to print to stdout, not stderr
|
|
8210
|
+
// calling message() adds the "[command name]" prefix
|
|
8211
|
+
_message(utils.toArray(arguments));
|
|
8212
|
+
STDOUT = false;
|
|
8033
8213
|
}
|
|
8034
8214
|
|
|
8035
|
-
function
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
k;
|
|
8040
|
-
if (rev) {
|
|
8041
|
-
if (same) error('copy error');
|
|
8042
|
-
inc = -1;
|
|
8043
|
-
offs = n - 1;
|
|
8044
|
-
}
|
|
8045
|
-
if (same && j > i) {
|
|
8046
|
-
for (k=n-1; k>=0; k--) {
|
|
8047
|
-
dest[j + k] = src[i + k];
|
|
8048
|
-
}
|
|
8049
|
-
} else {
|
|
8050
|
-
for (k=0; k<n; k++, offs += inc) {
|
|
8051
|
-
dest[k + j] = src[i + offs];
|
|
8052
|
-
}
|
|
8215
|
+
function verbose() {
|
|
8216
|
+
// verbose can be set globally with the -verbose command or separately for each command
|
|
8217
|
+
if (useVerbose()) {
|
|
8218
|
+
message.apply(null, arguments);
|
|
8053
8219
|
}
|
|
8054
8220
|
}
|
|
8055
8221
|
|
|
8056
|
-
function
|
|
8057
|
-
|
|
8058
|
-
var n = copyLen || src.length;
|
|
8059
|
-
var dest = new src.constructor(len);
|
|
8060
|
-
copyElements(src, 0, dest, 0, n);
|
|
8061
|
-
return dest;
|
|
8222
|
+
function useVerbose() {
|
|
8223
|
+
return getStashedVar('VERBOSE');
|
|
8062
8224
|
}
|
|
8063
8225
|
|
|
8064
|
-
function
|
|
8065
|
-
|
|
8066
|
-
if (name1 && name2) {
|
|
8067
|
-
merged = findStringPrefix(name1, name2).replace(/[-_]$/, '');
|
|
8068
|
-
}
|
|
8069
|
-
return merged || '';
|
|
8226
|
+
function useDebug() {
|
|
8227
|
+
return getStashedVar('DEBUG');
|
|
8070
8228
|
}
|
|
8071
8229
|
|
|
8072
|
-
function
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
if (a[i] !== b[i]) break;
|
|
8230
|
+
function debug() {
|
|
8231
|
+
if (useDebug()) {
|
|
8232
|
+
logArgs(arguments);
|
|
8076
8233
|
}
|
|
8077
|
-
return a.substr(0, i);
|
|
8078
8234
|
}
|
|
8079
8235
|
|
|
8080
|
-
function
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
var pct;
|
|
8084
|
-
if (isPct) {
|
|
8085
|
-
pct = Number(str.replace('%', '')) / 100;
|
|
8086
|
-
} else {
|
|
8087
|
-
pct = Number(str);
|
|
8088
|
-
}
|
|
8089
|
-
if (!(pct >= 0 && pct <= 1)) {
|
|
8090
|
-
stop(format("Invalid percentage: %s", str));
|
|
8236
|
+
function time(slug) {
|
|
8237
|
+
if (useDebug()) {
|
|
8238
|
+
console.time(slug);
|
|
8091
8239
|
}
|
|
8092
|
-
return pct;
|
|
8093
8240
|
}
|
|
8094
8241
|
|
|
8095
|
-
function
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
suffix = '-' + suffix;
|
|
8242
|
+
function timeEnd(slug) {
|
|
8243
|
+
if (useDebug()) {
|
|
8244
|
+
console.timeEnd(slug);
|
|
8099
8245
|
}
|
|
8100
|
-
return name + suffix;
|
|
8101
8246
|
}
|
|
8102
8247
|
|
|
8103
|
-
function
|
|
8104
|
-
var
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
candidate != name && counts[candidate] > 0) { // versioned name is a preexisting name
|
|
8116
|
-
i++;
|
|
8117
|
-
versionedName = format(name, i);
|
|
8118
|
-
if (!versionedName || versionedName == candidate) {
|
|
8119
|
-
throw new Error("Naming error"); // catch buggy versioning function
|
|
8120
|
-
}
|
|
8121
|
-
candidate = versionedName;
|
|
8248
|
+
function printError(err) {
|
|
8249
|
+
var msg;
|
|
8250
|
+
if (!LOGGING) return;
|
|
8251
|
+
if (utils.isString(err)) {
|
|
8252
|
+
err = new UserError(err);
|
|
8253
|
+
}
|
|
8254
|
+
if (err.name == 'NonFatalError') {
|
|
8255
|
+
console.error(messageArgs([err.message]).join(' '));
|
|
8256
|
+
} else if (err.name == 'UserError') {
|
|
8257
|
+
msg = err.message;
|
|
8258
|
+
if (!/Error/.test(msg)) {
|
|
8259
|
+
msg = "Error: " + msg;
|
|
8122
8260
|
}
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8261
|
+
console.error(messageArgs([msg]).join(' '));
|
|
8262
|
+
console.error("Run mapshaper -h to view help");
|
|
8263
|
+
} else {
|
|
8264
|
+
// not a user error (i.e. a bug in mapshaper)
|
|
8265
|
+
console.error(err);
|
|
8266
|
+
// throw err;
|
|
8267
|
+
}
|
|
8126
8268
|
}
|
|
8127
8269
|
|
|
8270
|
+
function UserError(msg) {
|
|
8271
|
+
var err = new Error(msg);
|
|
8272
|
+
err.name = 'UserError';
|
|
8273
|
+
return err;
|
|
8274
|
+
}
|
|
8128
8275
|
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8276
|
+
function NonFatalError(msg) {
|
|
8277
|
+
var err = new Error(msg);
|
|
8278
|
+
err.name = 'NonFatalError';
|
|
8279
|
+
return err;
|
|
8132
8280
|
}
|
|
8133
8281
|
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8282
|
+
function formatColumns(arr, alignments) {
|
|
8283
|
+
var widths = arr.reduce(function(memo, line) {
|
|
8284
|
+
return line.map(function(str, i) {
|
|
8285
|
+
return memo ? Math.max(memo[i], str.length) : str.length;
|
|
8286
|
+
});
|
|
8287
|
+
}, null);
|
|
8288
|
+
return arr.map(function(line) {
|
|
8289
|
+
line = line.map(function(str, i) {
|
|
8290
|
+
var rt = alignments && alignments[i] == 'right';
|
|
8291
|
+
var pad = (rt ? str.padStart : str.padEnd).bind(str);
|
|
8292
|
+
return pad(widths[i], ' ');
|
|
8293
|
+
});
|
|
8294
|
+
return ' ' + line.join(' ');
|
|
8295
|
+
}).join('\n');
|
|
8140
8296
|
}
|
|
8141
8297
|
|
|
8142
|
-
|
|
8143
|
-
|
|
8298
|
+
// Format an array of (preferably short) strings in columns for console logging.
|
|
8299
|
+
function formatStringsAsGrid(arr, width) {
|
|
8300
|
+
// TODO: variable column width
|
|
8301
|
+
var longest = arr.reduce(function(len, str) {
|
|
8302
|
+
return Math.max(len, str.length);
|
|
8303
|
+
}, 0),
|
|
8304
|
+
colWidth = longest + 2,
|
|
8305
|
+
perLine = Math.floor((width || 80) / colWidth) || 1;
|
|
8306
|
+
return arr.reduce(function(memo, name, i) {
|
|
8307
|
+
var col = i % perLine;
|
|
8308
|
+
if (i > 0 && col === 0) memo += '\n';
|
|
8309
|
+
if (col < perLine - 1) { // right-pad all but rightmost column
|
|
8310
|
+
name = utils.rpad(name, colWidth - 2, ' ');
|
|
8311
|
+
}
|
|
8312
|
+
return memo + ' ' + name;
|
|
8313
|
+
}, '');
|
|
8144
8314
|
}
|
|
8145
8315
|
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
return isNaN(parsed) ? null : parsed;
|
|
8316
|
+
// expose so GUI can use it
|
|
8317
|
+
function formatLogArgs(args) {
|
|
8318
|
+
return utils.toArray(args).join(' ');
|
|
8150
8319
|
}
|
|
8151
8320
|
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8321
|
+
function messageArgs(args) {
|
|
8322
|
+
var arr = utils.toArray(args);
|
|
8323
|
+
var cmd = getStashedVar('current_command');
|
|
8324
|
+
if (cmd && cmd != 'help') {
|
|
8325
|
+
arr.unshift('[' + cmd + ']');
|
|
8326
|
+
}
|
|
8327
|
+
return arr;
|
|
8155
8328
|
}
|
|
8156
8329
|
|
|
8157
|
-
function
|
|
8158
|
-
|
|
8159
|
-
|
|
8330
|
+
function logArgs(args) {
|
|
8331
|
+
if (!LOGGING || getStashedVar('QUIET') || !utils.isArrayLike(args)) return;
|
|
8332
|
+
var msg = formatLogArgs(args);
|
|
8333
|
+
if (STDOUT) console.log(msg);
|
|
8334
|
+
else console.error(msg);
|
|
8160
8335
|
}
|
|
8161
8336
|
|
|
8162
|
-
function
|
|
8163
|
-
|
|
8164
|
-
if (
|
|
8165
|
-
|
|
8166
|
-
last = str.charAt(len-1);
|
|
8167
|
-
// if (first == '"' && last == '"' && !str.includes('","') ||
|
|
8168
|
-
// first == "'" && last == "'" && !str.includes("','")) {
|
|
8169
|
-
// don't strip if there are unescaped quotes
|
|
8170
|
-
// e.g. expressions that start and end with quotes
|
|
8171
|
-
// e.g. comma-separated list of quoted values
|
|
8172
|
-
if (first == '"' && last == '"' && !/[^\\]"./.test(str) ||
|
|
8173
|
-
first == "'" && last == "'" && !/[^\\]'./.test(str)) {
|
|
8174
|
-
str = str.substr(1, len-2);
|
|
8175
|
-
// remove string escapes
|
|
8176
|
-
str = str.replace(first == '"' ? /\\(?=")/g : /\\(?=')/g, '');
|
|
8177
|
-
}
|
|
8337
|
+
function truncateString(str, maxLen) {
|
|
8338
|
+
maxLen = maxLen || 80;
|
|
8339
|
+
if (str.length > maxLen) {
|
|
8340
|
+
str = str.substring(0, maxLen - 3).trimEnd() + '...';
|
|
8178
8341
|
}
|
|
8179
8342
|
return str;
|
|
8180
8343
|
}
|
|
@@ -8415,7 +8578,7 @@
|
|
|
8415
8578
|
} else if (geoType == 'polygon') {
|
|
8416
8579
|
test = polygonTest;
|
|
8417
8580
|
} else {
|
|
8418
|
-
error$
|
|
8581
|
+
error$2("Unexpected geometry type:", geoType);
|
|
8419
8582
|
}
|
|
8420
8583
|
return test;
|
|
8421
8584
|
|
|
@@ -11091,7 +11254,7 @@
|
|
|
11091
11254
|
hit.on('drag', function(e) {
|
|
11092
11255
|
if (!labelSelected(e) || activeId == -1) return;
|
|
11093
11256
|
if (e.id != activeId) {
|
|
11094
|
-
error$
|
|
11257
|
+
error$2("Mismatched hit ids:", e.id, activeId);
|
|
11095
11258
|
}
|
|
11096
11259
|
var scale = ext.getSymbolScale() || 1;
|
|
11097
11260
|
var symNode, textNode;
|
|
@@ -13467,9 +13630,12 @@
|
|
|
13467
13630
|
// is noticeably slower during animations with multiple canvases.
|
|
13468
13631
|
// Highlights are drawn on a separate canvas while hovering, because this
|
|
13469
13632
|
// is generally faster than redrawing all of the shapes.
|
|
13633
|
+
// Fast-nav forces the same path: _mainCanv has a CSS transform but stale
|
|
13634
|
+
// pixels, so painting on it would visually go through that transform a
|
|
13635
|
+
// second time and detach the highlight from its feature.
|
|
13470
13636
|
this.drawOverlayLayers = function(layers, action) {
|
|
13471
13637
|
var canv;
|
|
13472
|
-
if (action == 'hover') {
|
|
13638
|
+
if (action == 'hover' || _fastActive) {
|
|
13473
13639
|
canv = _overlayCanv;
|
|
13474
13640
|
_overlayCanv.prep(_ext);
|
|
13475
13641
|
} else {
|
|
@@ -14649,7 +14815,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
14649
14815
|
]
|
|
14650
14816
|
});
|
|
14651
14817
|
} else {
|
|
14652
|
-
error$
|
|
14818
|
+
error$2('unsupported map style:', style);
|
|
14653
14819
|
}
|
|
14654
14820
|
}
|
|
14655
14821
|
|
|
@@ -14876,6 +15042,200 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
14876
15042
|
};
|
|
14877
15043
|
}
|
|
14878
15044
|
|
|
15045
|
+
// Passive "Messages" inbox: a non-blocking alternative to modal alerts for
|
|
15046
|
+
// status messages and warnings. Backed by a header button (the envelope icon)
|
|
15047
|
+
// in #mode-buttons and a popup-dialog panel.
|
|
15048
|
+
//
|
|
15049
|
+
// Public API (attached to the gui instance):
|
|
15050
|
+
// gui.notify({severity, body, title?, dedupKey?}) add an entry
|
|
15051
|
+
// gui.notify(message) shorthand: info severity
|
|
15052
|
+
//
|
|
15053
|
+
// severity is one of 'info' | 'warn' | 'error' (default 'info').
|
|
15054
|
+
// If dedupKey is given and an entry with the same key already exists, the
|
|
15055
|
+
// existing entry's count is incremented and its timestamp updated instead of
|
|
15056
|
+
// adding a new row -- handy for repeated CLI warnings.
|
|
15057
|
+
|
|
15058
|
+
|
|
15059
|
+
var SEVERITIES = {info: true, warn: true, error: true};
|
|
15060
|
+
var PULSE_DURATION_MS = 1900; // matches the CSS animation total runtime
|
|
15061
|
+
|
|
15062
|
+
function MessageControl(gui) {
|
|
15063
|
+
var btn = gui.container.findChild('.messages-btn');
|
|
15064
|
+
var badge = btn.findChild('.messages-badge');
|
|
15065
|
+
var panel = gui.container.findChild('.messages-panel');
|
|
15066
|
+
var listEl = panel.findChild('.messages-list');
|
|
15067
|
+
var emptyEl = panel.findChild('.messages-empty');
|
|
15068
|
+
var clearBtn = new SimpleButton(panel.findChild('.messages-clear-btn'));
|
|
15069
|
+
var closeBtn = new SimpleButton(panel.findChild('.close2-btn'));
|
|
15070
|
+
var entries = [];
|
|
15071
|
+
var nextId = 1;
|
|
15072
|
+
var pulseTimer = null;
|
|
15073
|
+
var rendered = false;
|
|
15074
|
+
|
|
15075
|
+
// Start fully hidden until the first notification arrives, so the icon
|
|
15076
|
+
// doesn't take up header space when there's nothing to show.
|
|
15077
|
+
btn.addClass('hidden');
|
|
15078
|
+
gui.addMode('messages', turnOn, turnOff, btn);
|
|
15079
|
+
|
|
15080
|
+
closeBtn.on('click', function() {
|
|
15081
|
+
gui.clearMode();
|
|
15082
|
+
});
|
|
15083
|
+
|
|
15084
|
+
clearBtn.on('click', function() {
|
|
15085
|
+
if (entries.length === 0) return;
|
|
15086
|
+
entries = [];
|
|
15087
|
+
// Close the panel so the now-disabled envelope isn't holding open a
|
|
15088
|
+
// panel the user can no longer dismiss with a header click.
|
|
15089
|
+
gui.clearMode();
|
|
15090
|
+
renderList();
|
|
15091
|
+
updateBadge();
|
|
15092
|
+
});
|
|
15093
|
+
|
|
15094
|
+
// Public API
|
|
15095
|
+
gui.notify = function(opts) {
|
|
15096
|
+
if (typeof opts == 'string') {
|
|
15097
|
+
opts = {body: opts};
|
|
15098
|
+
}
|
|
15099
|
+
opts = opts || {};
|
|
15100
|
+
var severity = SEVERITIES[opts.severity] ? opts.severity : 'info';
|
|
15101
|
+
var body = opts.body == null ? '' : String(opts.body);
|
|
15102
|
+
var title = opts.title || null;
|
|
15103
|
+
if (!body && !title) return;
|
|
15104
|
+
|
|
15105
|
+
if (opts.dedupKey) {
|
|
15106
|
+
for (var i = 0; i < entries.length; i++) {
|
|
15107
|
+
if (entries[i].dedupKey === opts.dedupKey) {
|
|
15108
|
+
entries[i].count = (entries[i].count || 1) + 1;
|
|
15109
|
+
entries[i].time = new Date();
|
|
15110
|
+
// Promote severity if a later occurrence is more severe.
|
|
15111
|
+
if (severityRank(severity) > severityRank(entries[i].severity)) {
|
|
15112
|
+
entries[i].severity = severity;
|
|
15113
|
+
}
|
|
15114
|
+
renderList();
|
|
15115
|
+
pulseBadge();
|
|
15116
|
+
return;
|
|
15117
|
+
}
|
|
15118
|
+
}
|
|
15119
|
+
}
|
|
15120
|
+
|
|
15121
|
+
entries.unshift({
|
|
15122
|
+
id: nextId++,
|
|
15123
|
+
severity: severity,
|
|
15124
|
+
title: title,
|
|
15125
|
+
body: body,
|
|
15126
|
+
count: 1,
|
|
15127
|
+
time: new Date(),
|
|
15128
|
+
dedupKey: opts.dedupKey || null
|
|
15129
|
+
});
|
|
15130
|
+
renderList();
|
|
15131
|
+
updateBadge();
|
|
15132
|
+
pulseBadge();
|
|
15133
|
+
|
|
15134
|
+
// Mirror to the JS console so power users still see a record even if they
|
|
15135
|
+
// never open the panel. Use the matching console method per severity.
|
|
15136
|
+
var rec = (title ? title + ': ' : '') + body;
|
|
15137
|
+
if (severity === 'error') console.error(rec);
|
|
15138
|
+
else if (severity === 'warn') console.warn(rec);
|
|
15139
|
+
else if (typeof internal !== 'undefined' && internal.logArgs) {
|
|
15140
|
+
internal.logArgs([rec]);
|
|
15141
|
+
} else {
|
|
15142
|
+
console.log(rec);
|
|
15143
|
+
}
|
|
15144
|
+
};
|
|
15145
|
+
|
|
15146
|
+
function severityRank(s) {
|
|
15147
|
+
return s === 'error' ? 2 : s === 'warn' ? 1 : 0;
|
|
15148
|
+
}
|
|
15149
|
+
|
|
15150
|
+
function turnOn() {
|
|
15151
|
+
if (!rendered) renderList();
|
|
15152
|
+
panel.show();
|
|
15153
|
+
}
|
|
15154
|
+
|
|
15155
|
+
function turnOff() {
|
|
15156
|
+
panel.hide();
|
|
15157
|
+
}
|
|
15158
|
+
|
|
15159
|
+
function updateBadge() {
|
|
15160
|
+
var n = entries.length;
|
|
15161
|
+
var hasWarn = false;
|
|
15162
|
+
var hasError = false;
|
|
15163
|
+
for (var i = 0; i < entries.length; i++) {
|
|
15164
|
+
if (entries[i].severity === 'error') hasError = true;
|
|
15165
|
+
else if (entries[i].severity === 'warn') hasWarn = true;
|
|
15166
|
+
}
|
|
15167
|
+
badge.text(n > 99 ? '99+' : String(n));
|
|
15168
|
+
badge.classed('hidden', n === 0);
|
|
15169
|
+
badge.classed('warn', hasWarn && !hasError);
|
|
15170
|
+
badge.classed('error', hasError);
|
|
15171
|
+
btn.classed('hidden', n === 0);
|
|
15172
|
+
}
|
|
15173
|
+
|
|
15174
|
+
function pulseBadge() {
|
|
15175
|
+
btn.removeClass('pulse');
|
|
15176
|
+
// Force reflow so removing+adding the class restarts the CSS animation.
|
|
15177
|
+
void btn.node().offsetWidth;
|
|
15178
|
+
btn.addClass('pulse');
|
|
15179
|
+
if (pulseTimer) clearTimeout(pulseTimer);
|
|
15180
|
+
pulseTimer = setTimeout(function() {
|
|
15181
|
+
btn.removeClass('pulse');
|
|
15182
|
+
pulseTimer = null;
|
|
15183
|
+
}, PULSE_DURATION_MS);
|
|
15184
|
+
}
|
|
15185
|
+
|
|
15186
|
+
function renderList() {
|
|
15187
|
+
rendered = true;
|
|
15188
|
+
listEl.empty();
|
|
15189
|
+
if (entries.length === 0) {
|
|
15190
|
+
emptyEl.show();
|
|
15191
|
+
return;
|
|
15192
|
+
}
|
|
15193
|
+
emptyEl.hide();
|
|
15194
|
+
for (var i = 0; i < entries.length; i++) {
|
|
15195
|
+
listEl.node().appendChild(renderItem(entries[i]).node());
|
|
15196
|
+
}
|
|
15197
|
+
}
|
|
15198
|
+
|
|
15199
|
+
function renderItem(entry) {
|
|
15200
|
+
var item = El('div').addClass('message-item').addClass('severity-' + entry.severity);
|
|
15201
|
+
if (entry.title) {
|
|
15202
|
+
El('span').addClass('message-title').text(entry.title).appendTo(item);
|
|
15203
|
+
}
|
|
15204
|
+
var bodyText = entry.body || '';
|
|
15205
|
+
if (entry.count > 1) {
|
|
15206
|
+
bodyText += ' (\u00d7' + entry.count + ')';
|
|
15207
|
+
}
|
|
15208
|
+
El('div').addClass('message-body').text(bodyText).appendTo(item);
|
|
15209
|
+
El('span').addClass('message-time').text(formatTime(entry.time)).appendTo(item);
|
|
15210
|
+
var dismiss = El('span').addClass('message-dismiss').attr('title', 'Dismiss').text('\u00d7').appendTo(item);
|
|
15211
|
+
var entryId = entry.id;
|
|
15212
|
+
dismiss.on('click', function(e) {
|
|
15213
|
+
// Prevent the click from also triggering any panel-level handlers.
|
|
15214
|
+
if (e && e.stopPropagation) e.stopPropagation();
|
|
15215
|
+
entries = entries.filter(function(x) { return x.id !== entryId; });
|
|
15216
|
+
// If we just dismissed the last entry, close the panel for the same
|
|
15217
|
+
// reason as Clear all: the envelope will go disabled and the user
|
|
15218
|
+
// wouldn't be able to dismiss the panel by clicking the header again.
|
|
15219
|
+
if (entries.length === 0) gui.clearMode();
|
|
15220
|
+
renderList();
|
|
15221
|
+
updateBadge();
|
|
15222
|
+
});
|
|
15223
|
+
return item;
|
|
15224
|
+
}
|
|
15225
|
+
|
|
15226
|
+
function formatTime(d) {
|
|
15227
|
+
if (!(d instanceof Date)) return '';
|
|
15228
|
+
var hh = String(d.getHours()).padStart(2, '0');
|
|
15229
|
+
var mm = String(d.getMinutes()).padStart(2, '0');
|
|
15230
|
+
return hh + ':' + mm;
|
|
15231
|
+
}
|
|
15232
|
+
|
|
15233
|
+
return {
|
|
15234
|
+
notify: gui.notify,
|
|
15235
|
+
count: function() { return entries.length; }
|
|
15236
|
+
};
|
|
15237
|
+
}
|
|
15238
|
+
|
|
14879
15239
|
// import { ProjectOptions } from './gui-project-control';
|
|
14880
15240
|
|
|
14881
15241
|
|
|
@@ -14897,6 +15257,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
14897
15257
|
gui.keyboard = new KeyboardEvents(gui);
|
|
14898
15258
|
gui.buttons = new SidebarButtons(gui);
|
|
14899
15259
|
gui.display = new DisplayOptions(gui);
|
|
15260
|
+
gui.messages = new MessageControl(gui);
|
|
14900
15261
|
gui.basemap = new Basemap(gui);
|
|
14901
15262
|
gui.session = new SessionHistory(gui);
|
|
14902
15263
|
gui.contextMenu = new ContextMenu();
|
|
@@ -14988,6 +15349,25 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
14988
15349
|
// This is the entry point for bundling mapshaper's web UI
|
|
14989
15350
|
|
|
14990
15351
|
|
|
15352
|
+
// Refresh detection for mapshaper-gui: if the previous incarnation of this
|
|
15353
|
+
// tab set the 'navigating away' marker on pagehide, then this page load is a
|
|
15354
|
+
// reload (not a fresh tab). Tell the server to cancel any pending /close,
|
|
15355
|
+
// otherwise the server's grace window can race past while the new page is
|
|
15356
|
+
// idle and terminate the process. sessionStorage is per-tab and survives a
|
|
15357
|
+
// refresh, but is gone after a real close -- exactly the signal we need.
|
|
15358
|
+
// This runs before onload to fire as early as possible in the page lifecycle.
|
|
15359
|
+
if (typeof window !== 'undefined' &&
|
|
15360
|
+
window.location.hostname === 'localhost' &&
|
|
15361
|
+
window.sessionStorage &&
|
|
15362
|
+
window.sessionStorage.getItem('mapshaper_navigating_away')) {
|
|
15363
|
+
window.sessionStorage.removeItem('mapshaper_navigating_away');
|
|
15364
|
+
if (navigator.sendBeacon) {
|
|
15365
|
+
navigator.sendBeacon('/cancel-close');
|
|
15366
|
+
} else {
|
|
15367
|
+
try { fetch('/cancel-close', {method: 'GET', keepalive: true}); } catch (err) {}
|
|
15368
|
+
}
|
|
15369
|
+
}
|
|
15370
|
+
|
|
14991
15371
|
onload(function() {
|
|
14992
15372
|
if (!GUI.browserIsSupported()) {
|
|
14993
15373
|
El("#mshp-not-supported").show();
|
|
@@ -15031,10 +15411,9 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
15031
15411
|
importOpts = getImportOpts(manifest),
|
|
15032
15412
|
gui = new GuiInstance('body');
|
|
15033
15413
|
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
// }
|
|
15414
|
+
if (!importOpts.files?.length) {
|
|
15415
|
+
El('body').removeClass('mapshaper-preload');
|
|
15416
|
+
}
|
|
15038
15417
|
|
|
15039
15418
|
new AlertControl(gui);
|
|
15040
15419
|
new IntersectionControl(gui);
|
|
@@ -15056,12 +15435,23 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
|
|
|
15056
15435
|
}
|
|
15057
15436
|
});
|
|
15058
15437
|
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15438
|
+
// Send termination signal to mapshaper-gui when the tab is closed.
|
|
15439
|
+
// Use 'pagehide' rather than 'unload' (the latter is increasingly suppressed
|
|
15440
|
+
// by Chrome/Edge for bfcache reasons), and use sendBeacon / keepalive fetch
|
|
15441
|
+
// because async XHR in the unload path is not guaranteed to be sent.
|
|
15442
|
+
// The sessionStorage marker is the refresh-vs-close signal: if a new page
|
|
15443
|
+
// loads in the same tab, it'll see the marker and send /cancel-close to
|
|
15444
|
+
// abort the pending exit (see the top of this file).
|
|
15445
|
+
window.addEventListener('pagehide', function(e) {
|
|
15446
|
+
if (window.location.hostname != 'localhost') return;
|
|
15447
|
+
if (e.persisted) return; // page is being cached, not actually closed
|
|
15448
|
+
try { window.sessionStorage.setItem('mapshaper_navigating_away', '1'); } catch (err) {}
|
|
15449
|
+
if (navigator.sendBeacon) {
|
|
15450
|
+
navigator.sendBeacon('/close');
|
|
15451
|
+
} else {
|
|
15452
|
+
try {
|
|
15453
|
+
fetch('/close', {method: 'GET', keepalive: true});
|
|
15454
|
+
} catch (err) {}
|
|
15065
15455
|
}
|
|
15066
15456
|
});
|
|
15067
15457
|
|