mapshaper 0.7.67 → 0.7.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mapshaper.js +858 -410
- package/package.json +1 -1
- package/www/index.html +10 -3
- package/www/mapshaper-gui.js +417 -240
- package/www/mapshaper.js +858 -410
- package/www/page.css +261 -112
package/www/mapshaper-gui.js
CHANGED
|
@@ -9531,7 +9531,7 @@
|
|
|
9531
9531
|
|
|
9532
9532
|
function Console(gui) {
|
|
9533
9533
|
var model = gui.model;
|
|
9534
|
-
var CURSOR = '
|
|
9534
|
+
var CURSOR = '> ';
|
|
9535
9535
|
var PROMPT = 'Enter mapshaper commands or type "tips" for console help.';
|
|
9536
9536
|
var el = gui.container.findChild('.console').hide();
|
|
9537
9537
|
var content = el.findChild('.console-buffer');
|
|
@@ -9548,15 +9548,6 @@
|
|
|
9548
9548
|
var history = [];
|
|
9549
9549
|
var historyId = 0;
|
|
9550
9550
|
var _isOpen = false;
|
|
9551
|
-
var btn = gui.container.findChild('.console-btn')
|
|
9552
|
-
.on('click', toggle)
|
|
9553
|
-
.on('keydown', function(e) {
|
|
9554
|
-
if (e.key == 'Enter' || e.key == ' ') {
|
|
9555
|
-
e.preventDefault();
|
|
9556
|
-
e.stopPropagation();
|
|
9557
|
-
toggle();
|
|
9558
|
-
}
|
|
9559
|
-
});
|
|
9560
9551
|
var globals = {}; // share user-defined globals (job.defs) between runs
|
|
9561
9552
|
var sharedVars = {}; // share -vars / -defaults templating scope between runs
|
|
9562
9553
|
|
|
@@ -9588,10 +9579,6 @@
|
|
|
9588
9579
|
}
|
|
9589
9580
|
});
|
|
9590
9581
|
|
|
9591
|
-
function toggle() {
|
|
9592
|
-
gui.toggleSidebarPanel('console');
|
|
9593
|
-
}
|
|
9594
|
-
|
|
9595
9582
|
gui.on('sidebar', function(e) {
|
|
9596
9583
|
if (e.panels.includes('console')) {
|
|
9597
9584
|
turnOn();
|
|
@@ -9642,7 +9629,6 @@
|
|
|
9642
9629
|
function turnOn() {
|
|
9643
9630
|
// if (!_isOpen && !model.isEmpty()) {
|
|
9644
9631
|
if (!_isOpen) {
|
|
9645
|
-
btn.addClass('active').attr('aria-expanded', 'true');
|
|
9646
9632
|
_isOpen = true;
|
|
9647
9633
|
// Route logging output to the in-app console while it's open, so a
|
|
9648
9634
|
// user typing CLI commands here sees the results inline -- the same
|
|
@@ -9663,7 +9649,6 @@
|
|
|
9663
9649
|
|
|
9664
9650
|
function turnOff() {
|
|
9665
9651
|
if (_isOpen) {
|
|
9666
|
-
btn.removeClass('active').attr('aria-expanded', 'false');
|
|
9667
9652
|
_isOpen = false;
|
|
9668
9653
|
if (GUI.isActiveInstance(gui)) {
|
|
9669
9654
|
setLoggingForGUI(gui); // reset stop, message and error functions
|
|
@@ -10237,14 +10222,14 @@
|
|
|
10237
10222
|
}
|
|
10238
10223
|
|
|
10239
10224
|
function printExamples() {
|
|
10240
|
-
printExample("See a list of all console commands", "
|
|
10241
|
-
printExample("Get help using a single command", "
|
|
10242
|
-
printExample("Get information about imported datasets", "
|
|
10243
|
-
printExample("Print bot/debug runtime context as JSON", "
|
|
10244
|
-
printExample("Display browser session as shell commands", "
|
|
10245
|
-
printExample("Delete one state from a national dataset","
|
|
10246
|
-
printExample("Aggregate counties to states by dissolving shared edges" ,"
|
|
10247
|
-
printExample("Clear the console", "
|
|
10225
|
+
printExample("See a list of all console commands", CURSOR + "help");
|
|
10226
|
+
printExample("Get help using a single command", CURSOR + "help innerlines");
|
|
10227
|
+
printExample("Get information about imported datasets", CURSOR + "info");
|
|
10228
|
+
printExample("Print bot/debug runtime context as JSON", CURSOR + "context");
|
|
10229
|
+
printExample("Display browser session as shell commands", CURSOR + "history");
|
|
10230
|
+
printExample("Delete one state from a national dataset", CURSOR + "filter 'STATE != \"Alaska\"'");
|
|
10231
|
+
printExample("Aggregate counties to states by dissolving shared edges" , CURSOR + "dissolve 'STATE'");
|
|
10232
|
+
printExample("Clear the console", CURSOR + "clear");
|
|
10248
10233
|
}
|
|
10249
10234
|
|
|
10250
10235
|
function renderStructuredConsoleMessage(msg) {
|
|
@@ -11219,6 +11204,92 @@
|
|
|
11219
11204
|
};
|
|
11220
11205
|
}
|
|
11221
11206
|
|
|
11207
|
+
// Short descriptions of what a layer holds, for the contents column of the
|
|
11208
|
+
// layer panel: "6 polygons", "1 data record", "12 labels".
|
|
11209
|
+
//
|
|
11210
|
+
// A point layer is described by how its features are drawn, in the terms of
|
|
11211
|
+
// the point style panel: labels, symbols, circles and plain points. A layer
|
|
11212
|
+
// that mixes them lists each kind it has, e.g. "12 labels, 30 points".
|
|
11213
|
+
//
|
|
11214
|
+
// Features with null geometry are left out of the counts, which say what is on
|
|
11215
|
+
// the map. The column is too narrow to list them as well, so they go in a
|
|
11216
|
+
// tooltip with the full description.
|
|
11217
|
+
|
|
11218
|
+
var POINT_KINDS = ['label', 'symbol', 'circle', 'point'];
|
|
11219
|
+
|
|
11220
|
+
// How a point feature is drawn, given its data record.
|
|
11221
|
+
// A label wins over any symbol or circle drawn with it.
|
|
11222
|
+
function getPointKind(d) {
|
|
11223
|
+
if (!d) return 'point';
|
|
11224
|
+
if ('label-text' in d) return 'label';
|
|
11225
|
+
if (d['svg-symbol'] || d.icon) return 'symbol';
|
|
11226
|
+
if (d.r > 0) return 'circle';
|
|
11227
|
+
return 'point';
|
|
11228
|
+
}
|
|
11229
|
+
|
|
11230
|
+
// The kind of feature a point layer is set up for, from its field names:
|
|
11231
|
+
// names the features of an empty layer, e.g. "0 labels".
|
|
11232
|
+
function getPointLayerKind(fields) {
|
|
11233
|
+
if (fields.includes('label-text')) return 'label';
|
|
11234
|
+
if (fields.includes('svg-symbol') || fields.includes('icon')) return 'symbol';
|
|
11235
|
+
if (fields.includes('r')) return 'circle';
|
|
11236
|
+
return 'point';
|
|
11237
|
+
}
|
|
11238
|
+
|
|
11239
|
+
// Same test as the -info command's null shape count
|
|
11240
|
+
function isNullShape(shp) {
|
|
11241
|
+
return !shp || shp.length === 0;
|
|
11242
|
+
}
|
|
11243
|
+
|
|
11244
|
+
// @geometryType: 'point', 'polygon' or 'polyline'
|
|
11245
|
+
// @shapes: the layer's shapes
|
|
11246
|
+
// @records: data records, or null for a layer without attribute data
|
|
11247
|
+
// @fields: names of the layer's data fields
|
|
11248
|
+
// Returns {text, nulls, title}; title is the tooltip, or null if there are no nulls.
|
|
11249
|
+
function describeFeatureContents(geometryType, shapes, records, fields) {
|
|
11250
|
+
var nulls = shapes.length - countNonNull(shapes);
|
|
11251
|
+
var text = geometryType == 'point' ?
|
|
11252
|
+
describePoints(shapes, records, fields || []) :
|
|
11253
|
+
formatCount(shapes.length - nulls, geometryType);
|
|
11254
|
+
return {
|
|
11255
|
+
text: text,
|
|
11256
|
+
nulls: nulls,
|
|
11257
|
+
title: nulls > 0 ? text + ', ' + formatInteger(nulls) + ' without geometry' : null
|
|
11258
|
+
};
|
|
11259
|
+
}
|
|
11260
|
+
|
|
11261
|
+
function describePoints(shapes, records, fields) {
|
|
11262
|
+
var counts = {label: 0, symbol: 0, circle: 0, point: 0};
|
|
11263
|
+
var parts, i;
|
|
11264
|
+
for (i=0; i<shapes.length; i++) {
|
|
11265
|
+
if (isNullShape(shapes[i])) continue;
|
|
11266
|
+
counts[getPointKind(records ? records[i] : null)]++;
|
|
11267
|
+
}
|
|
11268
|
+
parts = POINT_KINDS.filter(function(kind) {
|
|
11269
|
+
return counts[kind] > 0;
|
|
11270
|
+
}).map(function(kind) {
|
|
11271
|
+
return formatCount(counts[kind], kind);
|
|
11272
|
+
});
|
|
11273
|
+
return parts.length > 0 ? parts.join(', ') :
|
|
11274
|
+
formatCount(0, getPointLayerKind(fields));
|
|
11275
|
+
}
|
|
11276
|
+
|
|
11277
|
+
function countNonNull(shapes) {
|
|
11278
|
+
var n = 0;
|
|
11279
|
+
for (var i=0; i<shapes.length; i++) {
|
|
11280
|
+
if (!isNullShape(shapes[i])) n++;
|
|
11281
|
+
}
|
|
11282
|
+
return n;
|
|
11283
|
+
}
|
|
11284
|
+
|
|
11285
|
+
function formatCount(n, noun) {
|
|
11286
|
+
return formatInteger(n) + ' ' + noun + (n == 1 ? '' : 's');
|
|
11287
|
+
}
|
|
11288
|
+
|
|
11289
|
+
function formatInteger(n) {
|
|
11290
|
+
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
11291
|
+
}
|
|
11292
|
+
|
|
11222
11293
|
var openMenu$1;
|
|
11223
11294
|
var openMenuId;
|
|
11224
11295
|
// One menu element per parent element, created on demand.
|
|
@@ -11612,7 +11683,6 @@
|
|
|
11612
11683
|
var el = gui.container.findChild(".layer-control").hide();
|
|
11613
11684
|
var btn = gui.container.findChild('.layer-control-btn');
|
|
11614
11685
|
var headerBtn = btn.findChild('.active-layer-label');
|
|
11615
|
-
var tab = gui.container.findChild('.layer-tab');
|
|
11616
11686
|
var isOpen = false;
|
|
11617
11687
|
var cache = new DomCache();
|
|
11618
11688
|
var pinAll = el.findChild('.pin-all'); // button for toggling layer visibility
|
|
@@ -11647,17 +11717,6 @@
|
|
|
11647
11717
|
gui.toggleSidebarPanel('console');
|
|
11648
11718
|
}
|
|
11649
11719
|
});
|
|
11650
|
-
tab.on('click', toggle).on('keydown', function(e) {
|
|
11651
|
-
if (e.key == 'Enter') {
|
|
11652
|
-
e.preventDefault();
|
|
11653
|
-
e.stopPropagation();
|
|
11654
|
-
toggle();
|
|
11655
|
-
} else if (e.key == ' ') {
|
|
11656
|
-
e.preventDefault();
|
|
11657
|
-
e.stopPropagation();
|
|
11658
|
-
gui.toggleSidebarPanel('console');
|
|
11659
|
-
}
|
|
11660
|
-
});
|
|
11661
11720
|
|
|
11662
11721
|
function toggle() {
|
|
11663
11722
|
gui.toggleSidebarPanel('layers');
|
|
@@ -11763,7 +11822,6 @@
|
|
|
11763
11822
|
function turnOn() {
|
|
11764
11823
|
if (isOpen) return;
|
|
11765
11824
|
isOpen = true;
|
|
11766
|
-
tab.addClass('active').attr('aria-expanded', 'true');
|
|
11767
11825
|
render();
|
|
11768
11826
|
el.addClass('open');
|
|
11769
11827
|
el.show();
|
|
@@ -11773,7 +11831,6 @@
|
|
|
11773
11831
|
if (!isOpen) return;
|
|
11774
11832
|
stopDragging();
|
|
11775
11833
|
isOpen = false;
|
|
11776
|
-
tab.removeClass('active').attr('aria-expanded', 'false');
|
|
11777
11834
|
el.removeClass('open');
|
|
11778
11835
|
el.hide();
|
|
11779
11836
|
}
|
|
@@ -11854,8 +11911,7 @@
|
|
|
11854
11911
|
|
|
11855
11912
|
function renderLayer(lyr, dataset, opts) {
|
|
11856
11913
|
var classes = 'layer-item';
|
|
11857
|
-
var
|
|
11858
|
-
var entry, html;
|
|
11914
|
+
var html;
|
|
11859
11915
|
|
|
11860
11916
|
if (opts.pinnable) classes += ' pinnable';
|
|
11861
11917
|
if (map.isActiveLayer(lyr)) classes += ' active';
|
|
@@ -11863,8 +11919,9 @@
|
|
|
11863
11919
|
if (lyr.pinned) classes += ' pinned';
|
|
11864
11920
|
|
|
11865
11921
|
html = '<!-- ' + lyr.menu_id + '--><div class="' + classes + '">';
|
|
11866
|
-
html +=
|
|
11867
|
-
|
|
11922
|
+
html += '<div class="layer-row"><div class="layer-name-col">' +
|
|
11923
|
+
'<span class="layer-name colored-text dot-underline">' + formatLayerNameForDisplay(lyr.name) + '</span></div>' +
|
|
11924
|
+
renderContents(lyr, dataset) + '</div>';
|
|
11868
11925
|
html += '<span class="more-btn layer-btn" role="button" aria-label="More layer options"></span>';
|
|
11869
11926
|
if (opts.pinnable) {
|
|
11870
11927
|
html += '<img class="eye-btn black-eye layer-btn" draggable="false" src="images/eye.png">';
|
|
@@ -12114,29 +12171,34 @@
|
|
|
12114
12171
|
|
|
12115
12172
|
}
|
|
12116
12173
|
|
|
12174
|
+
// Returns {text, title}; title is a tooltip, or null
|
|
12117
12175
|
function describeLyr(lyr, dataset) {
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
str, type;
|
|
12121
|
-
if (lyr.data && !lyr.shapes) {
|
|
12122
|
-
type = 'data record';
|
|
12123
|
-
} else if (lyr.geometry_type) {
|
|
12124
|
-
type = lyr.geometry_type + ' feature';
|
|
12125
|
-
} else if (internal.layerHasRaster(lyr)) {
|
|
12126
|
-
type = 'raster layer';
|
|
12127
|
-
}
|
|
12128
|
-
if (isFrame) {
|
|
12129
|
-
str = internal.formatFrameSizeForDisplay(
|
|
12176
|
+
if (internal.isFrameLayer(lyr, dataset.arcs)) {
|
|
12177
|
+
return {text: internal.formatFrameSizeForDisplay(
|
|
12130
12178
|
internal.getFrameLayerData(lyr, dataset.arcs)
|
|
12131
|
-
);
|
|
12132
|
-
} else if (internal.layerHasRaster(lyr)) {
|
|
12133
|
-
str = utils$1.format('%,d x %,d %s', internal.getRasterWidth(lyr.raster), internal.getRasterHeight(lyr.raster), type);
|
|
12134
|
-
} else if (type) {
|
|
12135
|
-
str = utils$1.format('%,d %s%s', n, type, utils$1.pluralSuffix(n));
|
|
12136
|
-
} else {
|
|
12137
|
-
str = "[empty]";
|
|
12179
|
+
), title: null};
|
|
12138
12180
|
}
|
|
12139
|
-
|
|
12181
|
+
if (internal.layerHasRaster(lyr)) {
|
|
12182
|
+
return {text: utils$1.format('%,d x %,d raster', internal.getRasterWidth(lyr.raster),
|
|
12183
|
+
internal.getRasterHeight(lyr.raster)), title: null};
|
|
12184
|
+
}
|
|
12185
|
+
if (lyr.data && !lyr.shapes) {
|
|
12186
|
+
return {text: formatCount(internal.getFeatureCount(lyr), 'data record'), title: null};
|
|
12187
|
+
}
|
|
12188
|
+
if (lyr.geometry_type) {
|
|
12189
|
+
return describeFeatureContents(lyr.geometry_type, lyr.shapes,
|
|
12190
|
+
lyr.data ? lyr.data.getRecords() : null, lyr.data ? lyr.data.getFields() : []);
|
|
12191
|
+
}
|
|
12192
|
+
return {text: '[empty]', title: null};
|
|
12193
|
+
}
|
|
12194
|
+
|
|
12195
|
+
function renderContents(lyr, dataset) {
|
|
12196
|
+
var o = describeLyr(lyr, dataset);
|
|
12197
|
+
if (!o.title) {
|
|
12198
|
+
return '<div class="layer-contents">' + o.text + '</div>';
|
|
12199
|
+
}
|
|
12200
|
+
return '<div class="layer-contents has-nulls"><span title="' + o.title + '">' +
|
|
12201
|
+
o.text + '</span></div>';
|
|
12140
12202
|
}
|
|
12141
12203
|
|
|
12142
12204
|
function renderLayerInfo(info) {
|
|
@@ -12265,9 +12327,48 @@
|
|
|
12265
12327
|
return !!(lyr && (lyr.geometry_type == 'point' || lyr.geometry_type == 'polyline' || lyr.geometry_type == 'polygon'));
|
|
12266
12328
|
}
|
|
12267
12329
|
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12330
|
+
}
|
|
12331
|
+
|
|
12332
|
+
// The buttons that open and close the sidebar panels: an icon strip on the
|
|
12333
|
+
// map while the sidebar is closed, and a strip of toggles at the top of the
|
|
12334
|
+
// sidebar while it is open. Each button toggles its own panel, so both panels
|
|
12335
|
+
// can be open at once.
|
|
12336
|
+
function SidebarTabs(gui) {
|
|
12337
|
+
var buttons = gui.container.findChildren('.sidebar-tab, .sidebar-strip-btn');
|
|
12338
|
+
var hideBtn = gui.container.findChild('.sidebar-hide-btn');
|
|
12339
|
+
|
|
12340
|
+
buttons.forEach(function(btn) {
|
|
12341
|
+
var panel = btn.attr('data-panel');
|
|
12342
|
+
btn.on('click', function() {
|
|
12343
|
+
gui.toggleSidebarPanel(panel);
|
|
12344
|
+
});
|
|
12345
|
+
onKeyboardActivate(btn, function() {
|
|
12346
|
+
gui.toggleSidebarPanel(panel);
|
|
12347
|
+
});
|
|
12348
|
+
});
|
|
12349
|
+
|
|
12350
|
+
hideBtn.on('click', hideSidebar);
|
|
12351
|
+
onKeyboardActivate(hideBtn, hideSidebar);
|
|
12352
|
+
|
|
12353
|
+
gui.on('sidebar', function(e) {
|
|
12354
|
+
buttons.forEach(function(btn) {
|
|
12355
|
+
var open = e.panels.includes(btn.attr('data-panel'));
|
|
12356
|
+
btn.classed('active', open).attr('aria-expanded', String(open));
|
|
12357
|
+
});
|
|
12358
|
+
});
|
|
12359
|
+
|
|
12360
|
+
function hideSidebar() {
|
|
12361
|
+
gui.setSidebarPanels(null);
|
|
12362
|
+
}
|
|
12363
|
+
|
|
12364
|
+
function onKeyboardActivate(btn, action) {
|
|
12365
|
+
btn.on('keydown', function(e) {
|
|
12366
|
+
if (e.key == 'Enter' || e.key == ' ') {
|
|
12367
|
+
e.preventDefault();
|
|
12368
|
+
e.stopPropagation();
|
|
12369
|
+
action();
|
|
12370
|
+
}
|
|
12371
|
+
});
|
|
12271
12372
|
}
|
|
12272
12373
|
}
|
|
12273
12374
|
|
|
@@ -12400,7 +12501,9 @@
|
|
|
12400
12501
|
if (!container.node()) return;
|
|
12401
12502
|
DRAW_KINDS.forEach(function(kind, i) {
|
|
12402
12503
|
if (i > 0) {
|
|
12403
|
-
|
|
12504
|
+
// A breaking space after the dot, so that a sidebar too narrow for the
|
|
12505
|
+
// whole list wraps it between links, and never inside one.
|
|
12506
|
+
El('span').addClass('layer-menu-link-separator').html(' · ')
|
|
12404
12507
|
.appendTo(container);
|
|
12405
12508
|
}
|
|
12406
12509
|
El('span').addClass('layer-menu-link').attr('data-kind', kind.kind)
|
|
@@ -14234,25 +14337,25 @@
|
|
|
14234
14337
|
// labelModeIsAvailable().
|
|
14235
14338
|
var menus = {
|
|
14236
14339
|
standard: ['info', 'selection', 'box', 'ruler'],
|
|
14237
|
-
empty: ['
|
|
14238
|
-
polygons: ['info', 'selection', '
|
|
14239
|
-
rectangles: ['info', 'selection', '
|
|
14240
|
-
lines: ['info', 'selection', '
|
|
14340
|
+
empty: [ 'label','edit_points', 'edit_lines', 'edit_polygons','box', 'ruler'],
|
|
14341
|
+
polygons: ['info', 'selection', 'polygon_style', 'edit_polygons', 'box', 'ruler'],
|
|
14342
|
+
rectangles: ['info', 'selection', 'polygon_style', 'edit_polygons', 'rectangles', 'box', 'ruler'],
|
|
14343
|
+
lines: ['info', 'selection', 'line_style', 'edit_lines', 'snip_lines', 'box', 'ruler'],
|
|
14241
14344
|
table: ['info', 'selection'],
|
|
14242
|
-
raster: ['
|
|
14243
|
-
|
|
14244
|
-
|
|
14245
|
-
// with creating and retyping labels, so each of them offered a subset of
|
|
14246
|
-
// what sat next to it in the menu.
|
|
14247
|
-
labels: ['info', 'selection', 'box', 'label', 'ruler'],
|
|
14248
|
-
points: ['info', 'selection', 'box', 'point_style', 'edit_points', 'ruler'], // , 'add-points'
|
|
14345
|
+
raster: ['box', 'ruler'],
|
|
14346
|
+
labels: ['info', 'selection', 'label', 'box', 'ruler'],
|
|
14347
|
+
points: ['info', 'selection', 'edit_points', 'point_style', 'box', 'ruler'], // , 'add-points'
|
|
14249
14348
|
// An empty point layer is the layer the "Draw: labels" link creates,
|
|
14250
14349
|
// and a label goes into it rather than beside it (see labelWouldJoin), so
|
|
14251
14350
|
// the label tool belongs in its menu as well as the point tools: it is the
|
|
14252
14351
|
// way back into a labels layer that has no label in it yet.
|
|
14253
|
-
emptyPoints: ['info', 'selection', '
|
|
14352
|
+
emptyPoints: ['info', 'selection', 'label', 'point_style', 'edit_points', 'box', 'ruler']
|
|
14254
14353
|
};
|
|
14255
14354
|
|
|
14355
|
+
// Tools that work the same whatever the active layer is. They go below a
|
|
14356
|
+
// divider, so the layer-name heading visibly covers only the tools above it.
|
|
14357
|
+
var layerIndependentModes = ['box', 'ruler'];
|
|
14358
|
+
|
|
14256
14359
|
var prompts = {
|
|
14257
14360
|
box: 'Shift-drag to draw a box',
|
|
14258
14361
|
data: 'Click-select features to edit their attributes',
|
|
@@ -14348,7 +14451,7 @@
|
|
|
14348
14451
|
};
|
|
14349
14452
|
|
|
14350
14453
|
this.modeUsesPopup = function(mode) {
|
|
14351
|
-
return ['info', 'selection', 'data', 'box', 'edit_points'
|
|
14454
|
+
return ['info', 'selection', 'data', 'box', 'edit_points'].includes(mode);
|
|
14352
14455
|
};
|
|
14353
14456
|
|
|
14354
14457
|
this.modeSupportsUndo = function(mode) {
|
|
@@ -14419,27 +14522,54 @@
|
|
|
14419
14522
|
|
|
14420
14523
|
function renderMenu() {
|
|
14421
14524
|
if (!menu) return;
|
|
14422
|
-
var modes = getAvailableModes()
|
|
14423
|
-
menu.empty();
|
|
14424
|
-
modes.forEach(function(mode) {
|
|
14525
|
+
var modes = getAvailableModes().filter(function(mode) {
|
|
14425
14526
|
// don't show "turn off" link if not currently editing
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
setMode(mode);
|
|
14434
|
-
if (mode == 'off') closeMenu(120); // only close if turning off
|
|
14435
|
-
// closeMenu(mode == 'off' ? 120 : 400); // close after selecting
|
|
14436
|
-
}
|
|
14437
|
-
e.stopPropagation();
|
|
14438
|
-
});
|
|
14527
|
+
return !(_editMode == 'off' && mode == 'off');
|
|
14528
|
+
});
|
|
14529
|
+
var layerModes = modes.filter(function(mode) {
|
|
14530
|
+
return !layerIndependentModes.includes(mode);
|
|
14531
|
+
});
|
|
14532
|
+
var otherModes = modes.filter(function(mode) {
|
|
14533
|
+
return layerIndependentModes.includes(mode);
|
|
14439
14534
|
});
|
|
14535
|
+
var lyr = gui.model.getActiveLayer()?.layer;
|
|
14536
|
+
menu.empty();
|
|
14537
|
+
if (lyr && layerModes.length > 0) {
|
|
14538
|
+
renderLayerHeading(lyr);
|
|
14539
|
+
}
|
|
14540
|
+
layerModes.forEach(renderMenuItem);
|
|
14541
|
+
if (layerModes.length > 0 && otherModes.length > 0) {
|
|
14542
|
+
El('div').addClass('nav-menu-divider').appendTo(menu);
|
|
14543
|
+
}
|
|
14544
|
+
otherModes.forEach(renderMenuItem);
|
|
14440
14545
|
updateSelectionHighlight();
|
|
14441
14546
|
}
|
|
14442
14547
|
|
|
14548
|
+
// Names the layer the tools above the divider act on.
|
|
14549
|
+
function renderLayerHeading(lyr) {
|
|
14550
|
+
var name = formatLayerNameForDisplay(lyr.name);
|
|
14551
|
+
var heading = El('div').addClass('nav-menu-heading').attr('title', name).text(name).appendTo(menu);
|
|
14552
|
+
// A click on the heading would reach the button, which picks a tool.
|
|
14553
|
+
heading.on('click', function(e) {
|
|
14554
|
+
e.stopPropagation();
|
|
14555
|
+
});
|
|
14556
|
+
}
|
|
14557
|
+
|
|
14558
|
+
function renderMenuItem(mode) {
|
|
14559
|
+
var link = El('div').addClass('nav-menu-item').attr('data-name', mode).text(getModeLabel(mode)).appendTo(menu);
|
|
14560
|
+
link.on('click', function(e) {
|
|
14561
|
+
if (_editMode == mode) {
|
|
14562
|
+
// closeMenu();
|
|
14563
|
+
setMode('off');
|
|
14564
|
+
} else if (_editMode != mode) {
|
|
14565
|
+
setMode(mode);
|
|
14566
|
+
if (mode == 'off') closeMenu(120); // only close if turning off
|
|
14567
|
+
// closeMenu(mode == 'off' ? 120 : 400); // close after selecting
|
|
14568
|
+
}
|
|
14569
|
+
e.stopPropagation();
|
|
14570
|
+
});
|
|
14571
|
+
}
|
|
14572
|
+
|
|
14443
14573
|
function getModeLabel(mode) {
|
|
14444
14574
|
return labels[mode];
|
|
14445
14575
|
}
|
|
@@ -16565,23 +16695,38 @@
|
|
|
16565
16695
|
function makeColorField(parent, chit, input) {
|
|
16566
16696
|
var box = El('div').addClass('label-color-field').appendTo(parent);
|
|
16567
16697
|
chit.appendTo(box);
|
|
16568
|
-
input.appendTo(box);
|
|
16698
|
+
input.addClass('label-color-input').appendTo(box);
|
|
16569
16699
|
return box;
|
|
16570
16700
|
}
|
|
16571
16701
|
|
|
16572
|
-
// A colour
|
|
16573
|
-
//
|
|
16574
|
-
//
|
|
16702
|
+
// A colour field with its opacity at the right-hand end, behind a divider:
|
|
16703
|
+
// opacity qualifies the colour, and one border says so where two fields side
|
|
16704
|
+
// by side said they were separate settings. It also leaves the narrow column
|
|
16705
|
+
// of the row free for a field that needs it. The opacity has no caption; its
|
|
16706
|
+
// percent sign and its tooltip say what it is.
|
|
16707
|
+
//
|
|
16708
|
+
// Returns the field's box; the opacity input is made by makeOpacityInput().
|
|
16709
|
+
function makeColorOpacityField(parent, chit, input, opacityOpts) {
|
|
16710
|
+
var box = makeColorField(parent, chit, input).addClass('label-color-opacity-field');
|
|
16711
|
+
var opacity = makeOpacityInput(box, opacityOpts);
|
|
16712
|
+
return {box: box, opacity: opacity};
|
|
16713
|
+
}
|
|
16714
|
+
|
|
16715
|
+
// A colour and its opacity in one field, captioned, in the wide column of a
|
|
16716
|
+
// split row. Fill and Stroke are each one of these.
|
|
16575
16717
|
//
|
|
16576
|
-
// opts.label the caption over the
|
|
16718
|
+
// opts.label the caption over the field
|
|
16577
16719
|
// opts.onColor(hex) a colour was typed, picked or previewed to a finish
|
|
16578
16720
|
// opts.onOpacity(frac) a usable percentage was typed
|
|
16579
16721
|
// opts.revert() one that was not, so put the row back as it was
|
|
16722
|
+
//
|
|
16723
|
+
// control.aside is the row's narrow column, empty, for a field that belongs
|
|
16724
|
+
// beside the colour -- a stroke's width, say.
|
|
16580
16725
|
function makeColorRow(parent, opts) {
|
|
16581
16726
|
var row = El('div').addClass('label-style-row label-split-row').appendTo(parent);
|
|
16582
16727
|
var colorCell = El('div').addClass('label-split-cell label-color-row').appendTo(row);
|
|
16583
|
-
var
|
|
16584
|
-
var control = {row: row, chit: null, input: null, opacity: null, picker: null};
|
|
16728
|
+
var aside = El('div').addClass('label-split-cell').appendTo(row);
|
|
16729
|
+
var control = {row: row, aside: aside, chit: null, input: null, opacity: null, picker: null};
|
|
16585
16730
|
|
|
16586
16731
|
control.setColor = function(color) {
|
|
16587
16732
|
control.input.node().value = color || '';
|
|
@@ -16611,7 +16756,10 @@
|
|
|
16611
16756
|
if (isHexColor(color)) control.picker.setColor(color);
|
|
16612
16757
|
opts.onColor(color);
|
|
16613
16758
|
});
|
|
16614
|
-
|
|
16759
|
+
control.opacity = makeColorOpacityField(colorCell, control.chit, control.input, {
|
|
16760
|
+
onSet: opts.onOpacity,
|
|
16761
|
+
revert: opts.revert
|
|
16762
|
+
}).opacity;
|
|
16615
16763
|
control.picker = new ColorPicker(colorCell, {
|
|
16616
16764
|
presetRows: layerColorPresetRows,
|
|
16617
16765
|
// A drag in the picker shows on the map's own terms -- the swatch and the
|
|
@@ -16622,12 +16770,6 @@
|
|
|
16622
16770
|
opts.onColor(hex);
|
|
16623
16771
|
}
|
|
16624
16772
|
});
|
|
16625
|
-
|
|
16626
|
-
El('span').appendTo(opacityCell).text('Opacity');
|
|
16627
|
-
control.opacity = makeOpacityInput(opacityCell, {
|
|
16628
|
-
onSet: opts.onOpacity,
|
|
16629
|
-
revert: opts.revert
|
|
16630
|
-
});
|
|
16631
16773
|
return control;
|
|
16632
16774
|
}
|
|
16633
16775
|
|
|
@@ -17174,11 +17316,11 @@
|
|
|
17174
17316
|
// stroke of twice this.
|
|
17175
17317
|
var defaultHaloWidth = 2;
|
|
17176
17318
|
var defaultHaloColor = internal.svg.DEFAULT_HALO_COLOR;
|
|
17177
|
-
// The line height
|
|
17178
|
-
//
|
|
17179
|
-
//
|
|
17180
|
-
//
|
|
17181
|
-
var lineHeightPlaceholder =
|
|
17319
|
+
// The line height a label without one is drawn with, shown as a placeholder
|
|
17320
|
+
// rather than a value so that a label carries no line-height until one is
|
|
17321
|
+
// chosen. Shown as a bare multiple, which also says what a bare number typed
|
|
17322
|
+
// here means: a multiple of the font size, as in CSS, not px.
|
|
17323
|
+
var lineHeightPlaceholder = String(internal.svg.DEFAULT_LINE_HEIGHT);
|
|
17182
17324
|
// What a control shows for a selection whose labels disagree about it. One
|
|
17183
17325
|
// word, used in every field and menu that can be in that state: the panel
|
|
17184
17326
|
// otherwise says it by showing nothing, which is also what an unset property
|
|
@@ -17451,15 +17593,16 @@
|
|
|
17451
17593
|
clearLink = El('span').addClass('label-editing-clear colored-text').appendTo(selectRow).text('deselect').on('click', clearSelection);
|
|
17452
17594
|
|
|
17453
17595
|
// Three sections rather than one flat stack: the text, the symbol beside
|
|
17454
|
-
// it, and where the two sit relative to each other.
|
|
17455
|
-
//
|
|
17456
|
-
//
|
|
17457
|
-
// first rather than as a different kind of control.
|
|
17596
|
+
// it, and where the two sit relative to each other. Every section's colour
|
|
17597
|
+
// row is the same shape -- the colour with its opacity in one field, and
|
|
17598
|
+
// the value that qualifies it most closely beside it -- so that each reads
|
|
17599
|
+
// as a variation on the first rather than as a different kind of control.
|
|
17458
17600
|
var textSection = addSection('Text');
|
|
17459
17601
|
|
|
17460
|
-
// The controls whose own contents say what they are -- a font name, a
|
|
17461
|
-
//
|
|
17462
|
-
//
|
|
17602
|
+
// The controls whose own contents say what they are -- a font name, a
|
|
17603
|
+
// size beside a font style -- carry no label. The ones that would be a bare
|
|
17604
|
+
// number otherwise keep theirs, and so does a colour field, because the
|
|
17605
|
+
// field beside it has one and "Color" says the percentage is its opacity.
|
|
17463
17606
|
var fontRow = El('div').addClass('label-style-row').appendTo(textSection);
|
|
17464
17607
|
fontSelect = El('select').attr('title', 'Font').appendTo(fontRow).on('change', function() {
|
|
17465
17608
|
if (fontSelect.node().value) {
|
|
@@ -17485,11 +17628,20 @@
|
|
|
17485
17628
|
onDone: releaseFocus
|
|
17486
17629
|
});
|
|
17487
17630
|
|
|
17631
|
+
// Letter spacing beside the colour, above line height: the two spacing
|
|
17632
|
+
// values read as a pair in the narrow column, and the left of the row
|
|
17633
|
+
// below is where the alignment buttons go.
|
|
17634
|
+
//
|
|
17635
|
+
// A text block's width is not here: it is set by dragging the block's
|
|
17636
|
+
// handle, and a field for it would be clutter on every other label.
|
|
17488
17637
|
var colorRow = El('div').addClass('label-style-row label-split-row').appendTo(textSection);
|
|
17489
|
-
var textColorCell = El('div').addClass('label-split-cell label-color-row').appendTo(colorRow);
|
|
17638
|
+
var textColorCell = El('div').addClass('label-split-cell label-color-row label-text-color-row').appendTo(colorRow);
|
|
17639
|
+
El('span').appendTo(textColorCell).text('Color');
|
|
17490
17640
|
colorChit = El('div').addClass('label-color-chit').attr('role', 'button');
|
|
17491
17641
|
colorInput = El('input').attr('type', 'text').attr('title', 'Text color');
|
|
17492
|
-
|
|
17642
|
+
var textColorField = addColorOpacityField(textColorCell, colorChit, colorInput, applyLabelOpacity);
|
|
17643
|
+
colorFieldBox = textColorField.box;
|
|
17644
|
+
opacityInput = textColorField.opacity;
|
|
17493
17645
|
colorChit.on('click', function() {
|
|
17494
17646
|
if (this.classList.contains('disabled')) return;
|
|
17495
17647
|
toggleColorPicker();
|
|
@@ -17505,18 +17657,7 @@
|
|
|
17505
17657
|
});
|
|
17506
17658
|
colorPicker = initColorPicker(textColorCell, colorChit, colorInput, applyLabelColor);
|
|
17507
17659
|
|
|
17508
|
-
var
|
|
17509
|
-
opacityInput = addOpacityInput(opacityCell, applyLabelOpacity);
|
|
17510
|
-
|
|
17511
|
-
// Letter spacing takes the right-hand column on its own, above line
|
|
17512
|
-
// height: the two spacing values read as a pair there, and the left of the
|
|
17513
|
-
// row is where the alignment buttons go.
|
|
17514
|
-
//
|
|
17515
|
-
// A text block's width is not here: it is set by dragging the block's
|
|
17516
|
-
// handle, and a field for it would be clutter on every other label.
|
|
17517
|
-
var letterRow = El('div').addClass('label-style-row label-split-row').appendTo(textSection);
|
|
17518
|
-
El('div').addClass('label-split-cell').appendTo(letterRow);
|
|
17519
|
-
var letterCell = El('div').addClass('label-split-cell label-spacing-row').appendTo(letterRow);
|
|
17660
|
+
var letterCell = El('div').addClass('label-split-cell label-spacing-row').appendTo(colorRow);
|
|
17520
17661
|
El('span').appendTo(letterCell).text('Letter spacing');
|
|
17521
17662
|
letterSpacingInput = makeMeasureInput(letterCell, letterSpacingField, '0');
|
|
17522
17663
|
|
|
@@ -17554,8 +17695,8 @@
|
|
|
17554
17695
|
|
|
17555
17696
|
// A halo is a yes/no that its three values then qualify, as a symbol is, so
|
|
17556
17697
|
// it is switched the same way and its controls are inert while it is off.
|
|
17557
|
-
// Its
|
|
17558
|
-
//
|
|
17698
|
+
// Its row is the shape of the Text section's colour row: a colour and its
|
|
17699
|
+
// opacity, and a width in the narrow column beside them, where letter
|
|
17559
17700
|
// spacing sits above.
|
|
17560
17701
|
haloSection = addSection('Halo');
|
|
17561
17702
|
var haloTitle = haloSection.findChild('.label-style-section-title');
|
|
@@ -17566,10 +17707,13 @@
|
|
|
17566
17707
|
});
|
|
17567
17708
|
|
|
17568
17709
|
var haloColorRow = El('div').addClass('label-style-row label-split-row').appendTo(haloSection);
|
|
17569
|
-
var haloColorCell = El('div').addClass('label-split-cell label-color-row').appendTo(haloColorRow);
|
|
17710
|
+
var haloColorCell = El('div').addClass('label-split-cell label-color-row label-halo-color-row').appendTo(haloColorRow);
|
|
17711
|
+
El('span').appendTo(haloColorCell).text('Color');
|
|
17570
17712
|
haloColorChit = El('div').addClass('label-color-chit').attr('role', 'button');
|
|
17571
17713
|
haloColorInput = El('input').attr('type', 'text').attr('title', 'Halo color');
|
|
17572
|
-
|
|
17714
|
+
var haloColorField = addColorOpacityField(haloColorCell, haloColorChit, haloColorInput, applyHaloOpacity);
|
|
17715
|
+
haloColorFieldBox = haloColorField.box;
|
|
17716
|
+
haloOpacityInput = haloColorField.opacity;
|
|
17573
17717
|
haloColorChit.on('click', function() {
|
|
17574
17718
|
if (this.classList.contains('disabled')) return;
|
|
17575
17719
|
haloColorPicker.toggle();
|
|
@@ -17585,12 +17729,7 @@
|
|
|
17585
17729
|
});
|
|
17586
17730
|
haloColorPicker = initColorPicker(haloColorCell, haloColorChit, haloColorInput, applyHaloColor);
|
|
17587
17731
|
|
|
17588
|
-
var
|
|
17589
|
-
haloOpacityInput = addOpacityInput(haloOpacityCell, applyHaloOpacity);
|
|
17590
|
-
|
|
17591
|
-
var haloWidthRow = El('div').addClass('label-style-row label-split-row').appendTo(haloSection);
|
|
17592
|
-
El('div').addClass('label-split-cell').appendTo(haloWidthRow);
|
|
17593
|
-
var haloWidthCell = El('div').addClass('label-split-cell label-spacing-row').appendTo(haloWidthRow);
|
|
17732
|
+
var haloWidthCell = El('div').addClass('label-split-cell label-spacing-row').appendTo(haloColorRow);
|
|
17594
17733
|
El('span').appendTo(haloWidthCell).text('Width');
|
|
17595
17734
|
// Halves as well as whole pixels: a halo is usually 1 to 2px, and the
|
|
17596
17735
|
// step between those two is most of the range anyone uses.
|
|
@@ -17617,14 +17756,9 @@
|
|
|
17617
17756
|
className: 'label-icon-toggle',
|
|
17618
17757
|
onChange: setIconOn
|
|
17619
17758
|
});
|
|
17620
|
-
// The
|
|
17621
|
-
//
|
|
17622
|
-
|
|
17623
|
-
// pushes it out of line with the other.
|
|
17624
|
-
El('div').addClass('label-icon-size-caption').appendTo(iconTitle).text('Size');
|
|
17625
|
-
|
|
17626
|
-
var iconSizeRow = El('div').addClass('label-style-row label-split-row label-icon-shapes-row').appendTo(iconSection);
|
|
17627
|
-
var iconRow = El('div').addClass('label-split-cell').appendTo(iconSizeRow);
|
|
17759
|
+
// The shapes have the whole row, so there is room for more of them, and
|
|
17760
|
+
// the size sits beside the colour below, as the halo's width does.
|
|
17761
|
+
var iconRow = El('div').addClass('label-style-row label-icon-shapes-row').appendTo(iconSection);
|
|
17628
17762
|
var iconGroup = iconGroupEl = El('div').addClass('label-btn-group label-icon-buttons').appendTo(iconRow);
|
|
17629
17763
|
iconBtns = {};
|
|
17630
17764
|
iconTypes.forEach(function(icon) {
|
|
@@ -17637,21 +17771,14 @@
|
|
|
17637
17771
|
appendIconButtonSymbol(btn, icon.name);
|
|
17638
17772
|
});
|
|
17639
17773
|
|
|
17640
|
-
var sizeRow = El('div').addClass('label-split-cell label-icon-size-row').appendTo(iconSizeRow);
|
|
17641
|
-
iconSizeInput = new SizeField(sizeRow, {
|
|
17642
|
-
title: 'Symbol size in px',
|
|
17643
|
-
onSet: function(value) {
|
|
17644
|
-
applyIconSize(value);
|
|
17645
|
-
},
|
|
17646
|
-
onStep: nudgeIconSize,
|
|
17647
|
-
onDone: releaseFocus
|
|
17648
|
-
});
|
|
17649
|
-
|
|
17650
17774
|
var iconColorRow = El('div').addClass('label-style-row label-split-row').appendTo(iconSection);
|
|
17651
17775
|
var iconColorCell = El('div').addClass('label-split-cell label-color-row label-icon-color-row').appendTo(iconColorRow);
|
|
17776
|
+
El('span').appendTo(iconColorCell).text('Color');
|
|
17652
17777
|
iconColorChit = El('div').addClass('label-color-chit').attr('role', 'button');
|
|
17653
17778
|
iconColorInput = El('input').attr('type', 'text').attr('title', 'Symbol color');
|
|
17654
|
-
|
|
17779
|
+
var iconColorField = addColorOpacityField(iconColorCell, iconColorChit, iconColorInput, applyIconOpacity);
|
|
17780
|
+
iconColorFieldBox = iconColorField.box;
|
|
17781
|
+
iconOpacityInput = iconColorField.opacity;
|
|
17655
17782
|
iconColorChit.on('click', function() {
|
|
17656
17783
|
if (this.classList.contains('disabled')) return;
|
|
17657
17784
|
iconColorPicker.toggle();
|
|
@@ -17667,8 +17794,16 @@
|
|
|
17667
17794
|
});
|
|
17668
17795
|
iconColorPicker = initColorPicker(iconColorCell, iconColorChit, iconColorInput, applyIconColor);
|
|
17669
17796
|
|
|
17670
|
-
var
|
|
17671
|
-
|
|
17797
|
+
var sizeRow = El('div').addClass('label-split-cell label-icon-size-row').appendTo(iconColorRow);
|
|
17798
|
+
El('span').appendTo(sizeRow).text('Size');
|
|
17799
|
+
iconSizeInput = new SizeField(sizeRow, {
|
|
17800
|
+
title: 'Symbol size in px',
|
|
17801
|
+
onSet: function(value) {
|
|
17802
|
+
applyIconSize(value);
|
|
17803
|
+
},
|
|
17804
|
+
onStep: nudgeIconSize,
|
|
17805
|
+
onDone: releaseFocus
|
|
17806
|
+
});
|
|
17672
17807
|
|
|
17673
17808
|
initCalloutSection();
|
|
17674
17809
|
|
|
@@ -17780,10 +17915,13 @@
|
|
|
17780
17915
|
});
|
|
17781
17916
|
|
|
17782
17917
|
var colorRow = El('div').addClass('label-style-row label-split-row').appendTo(calloutSection);
|
|
17783
|
-
var colorCell = El('div').addClass('label-split-cell label-color-row').appendTo(colorRow);
|
|
17918
|
+
var colorCell = El('div').addClass('label-split-cell label-color-row label-callout-color-row').appendTo(colorRow);
|
|
17919
|
+
El('span').appendTo(colorCell).text('Color');
|
|
17784
17920
|
calloutColorChit = El('div').addClass('label-color-chit').attr('role', 'button');
|
|
17785
17921
|
calloutColorInput = El('input').attr('type', 'text').attr('title', 'Callout color');
|
|
17786
|
-
|
|
17922
|
+
var calloutColorField = addColorOpacityField(colorCell, calloutColorChit, calloutColorInput, applyCalloutOpacity);
|
|
17923
|
+
calloutColorFieldBox = calloutColorField.box;
|
|
17924
|
+
calloutOpacityInput = calloutColorField.opacity;
|
|
17787
17925
|
calloutColorChit.on('click', function() {
|
|
17788
17926
|
if (this.classList.contains('disabled')) return;
|
|
17789
17927
|
calloutColorPicker.toggle();
|
|
@@ -17798,14 +17936,10 @@
|
|
|
17798
17936
|
}
|
|
17799
17937
|
});
|
|
17800
17938
|
calloutColorPicker = initColorPicker(colorCell, calloutColorChit, calloutColorInput, applyCalloutColor);
|
|
17801
|
-
var opacityCell = El('div').addClass('label-split-cell label-opacity-row').appendTo(colorRow);
|
|
17802
|
-
calloutOpacityInput = addOpacityInput(opacityCell, applyCalloutOpacity);
|
|
17803
17939
|
|
|
17804
17940
|
// The gap in the narrow column, under the sizes: it has a blank state,
|
|
17805
17941
|
// "auto", which clears the anchor's symbol, so it is not a size field.
|
|
17806
|
-
var
|
|
17807
|
-
El('div').addClass('label-split-cell').appendTo(gapRow);
|
|
17808
|
-
var gapCell = El('div').addClass('label-split-cell label-spacing-row label-callout-gap-row').appendTo(gapRow);
|
|
17942
|
+
var gapCell = El('div').addClass('label-split-cell label-spacing-row label-callout-gap-row').appendTo(colorRow);
|
|
17809
17943
|
El('span').appendTo(gapCell).text('Gap');
|
|
17810
17944
|
calloutGapInput = El('input').attr('type', 'text').addClass('label-measure-input')
|
|
17811
17945
|
.attr('title', 'Space between the callout and the anchor, in px')
|
|
@@ -17887,9 +18021,9 @@
|
|
|
17887
18021
|
};
|
|
17888
18022
|
}
|
|
17889
18023
|
|
|
17890
|
-
function
|
|
17891
|
-
return
|
|
17892
|
-
onSet:
|
|
18024
|
+
function addColorOpacityField(parent, chit, input, onOpacity) {
|
|
18025
|
+
return makeColorOpacityField(parent, chit, input, {
|
|
18026
|
+
onSet: onOpacity,
|
|
17893
18027
|
revert: updateControls
|
|
17894
18028
|
});
|
|
17895
18029
|
}
|
|
@@ -19511,7 +19645,7 @@
|
|
|
19511
19645
|
|
|
19512
19646
|
fillControl = addColorControl(panel, 'Fill', 'fill', '');
|
|
19513
19647
|
strokeControl = addColorControl(panel, 'Stroke', 'stroke', '#000000');
|
|
19514
|
-
strokeWidthField = addStrokeWidthControl(
|
|
19648
|
+
strokeWidthField = addStrokeWidthControl(strokeControl.aside);
|
|
19515
19649
|
|
|
19516
19650
|
var buttonRow = El('div').addClass('label-style-row label-panel-button-row').appendTo(panel);
|
|
19517
19651
|
randomFillBtn = makePanelActionButton(buttonRow, 'Random fills', applyRandomFillColors);
|
|
@@ -19553,14 +19687,11 @@
|
|
|
19553
19687
|
return control;
|
|
19554
19688
|
}
|
|
19555
19689
|
|
|
19556
|
-
// In the narrow column
|
|
19690
|
+
// In the narrow column beside the stroke's colour. Stepping runs up a ladder
|
|
19557
19691
|
// of widths rather than by a fixed amount, because the useful ones are close
|
|
19558
19692
|
// together at the hairline end and far apart above 2px.
|
|
19559
|
-
function addStrokeWidthControl(
|
|
19560
|
-
|
|
19561
|
-
El('div').addClass('label-split-cell').appendTo(row);
|
|
19562
|
-
var cell = El('div').addClass('label-split-cell').appendTo(row);
|
|
19563
|
-
El('span').appendTo(cell).text('Stroke width');
|
|
19693
|
+
function addStrokeWidthControl(cell) {
|
|
19694
|
+
El('span').appendTo(cell).text('Width');
|
|
19564
19695
|
return new SizeField(cell, {
|
|
19565
19696
|
min: 0,
|
|
19566
19697
|
// Quarter-pixel widths are the useful hairlines, and the ladder below
|
|
@@ -20007,14 +20138,11 @@
|
|
|
20007
20138
|
circleStrokeControl = addCircleColorControl('Stroke', 'stroke');
|
|
20008
20139
|
circleControlRows.push(circleFillControl.row, circleStrokeControl.row);
|
|
20009
20140
|
|
|
20010
|
-
//
|
|
20011
|
-
//
|
|
20012
|
-
var
|
|
20013
|
-
|
|
20014
|
-
|
|
20015
|
-
var radiusCell = El('div').addClass('label-split-cell').appendTo(sizeRow);
|
|
20016
|
-
var widthCell = El('div').addClass('label-split-cell').appendTo(sizeRow);
|
|
20017
|
-
El('span').appendTo(widthCell).text('Stroke width');
|
|
20141
|
+
// Each size beside the colour it goes with: the radius beside the fill,
|
|
20142
|
+
// the stroke's width beside its colour, as in the line and polygon panels.
|
|
20143
|
+
var radiusCell = circleFillControl.aside;
|
|
20144
|
+
var widthCell = circleStrokeControl.aside;
|
|
20145
|
+
El('span').appendTo(widthCell).text('Width');
|
|
20018
20146
|
circleStrokeWidthField = new SizeField(widthCell, {
|
|
20019
20147
|
min: 0,
|
|
20020
20148
|
decimals: 2, // quarter-pixel hairlines, which the ladder below steps through
|
|
@@ -20045,7 +20173,6 @@
|
|
|
20045
20173
|
},
|
|
20046
20174
|
onDone: releaseFocus
|
|
20047
20175
|
});
|
|
20048
|
-
circleControlRows.push(sizeRow);
|
|
20049
20176
|
}
|
|
20050
20177
|
|
|
20051
20178
|
function initSymbolsSection() {
|
|
@@ -23102,8 +23229,11 @@
|
|
|
23102
23229
|
gui.on('interaction_mode_change', function(e) {
|
|
23103
23230
|
if (e.mode === 'selection') {
|
|
23104
23231
|
gui.enterMode('selection_tool');
|
|
23105
|
-
} else if (
|
|
23106
|
-
turnOff()
|
|
23232
|
+
} else if (gui.getMode() == 'selection_tool') {
|
|
23233
|
+
// Leave the gui mode rather than just calling turnOff(): a mode left
|
|
23234
|
+
// behind makes the next enterMode('selection_tool') a no-op, so the tool
|
|
23235
|
+
// would never turn back on.
|
|
23236
|
+
gui.clearMode();
|
|
23107
23237
|
}
|
|
23108
23238
|
});
|
|
23109
23239
|
|
|
@@ -26361,7 +26491,7 @@
|
|
|
26361
26491
|
|
|
26362
26492
|
function getLineHeight(o) {
|
|
26363
26493
|
var rec = o.pending ? o.pending.getStyle() : getRecord(o.target, o.id);
|
|
26364
|
-
return rec && rec['line-height']
|
|
26494
|
+
return internal.svg.getLineHeightDy(rec && rec['line-height']);
|
|
26365
26495
|
}
|
|
26366
26496
|
|
|
26367
26497
|
function getProvider(nodes) {
|
|
@@ -31489,50 +31619,73 @@
|
|
|
31489
31619
|
gui.on('interaction_mode_change', function(e) {
|
|
31490
31620
|
if (e.mode === 'rectangles') {
|
|
31491
31621
|
gui.enterMode('rectangle_tool');
|
|
31492
|
-
} else if (
|
|
31493
|
-
turnOff()
|
|
31622
|
+
} else if (gui.getMode() == 'rectangle_tool') {
|
|
31623
|
+
// Leave the gui mode rather than just calling turnOff(): a mode left
|
|
31624
|
+
// behind makes the next enterMode('rectangle_tool') a no-op, so the tool
|
|
31625
|
+
// would never turn back on.
|
|
31626
|
+
gui.clearMode();
|
|
31494
31627
|
}
|
|
31495
31628
|
});
|
|
31496
31629
|
|
|
31497
31630
|
hit.on('change', function(e) {
|
|
31498
31631
|
if (!_on) return;
|
|
31499
31632
|
// TODO: handle multiple hits (see gui-inspection-control)
|
|
31500
|
-
var id = e.id;
|
|
31501
31633
|
if (e.id > -1 && e.pinned) {
|
|
31502
|
-
|
|
31503
|
-
|
|
31504
|
-
var bbox = target.gui.displayArcs.getSimpleShapeBounds(path).toArray();
|
|
31505
|
-
box.setDataCoords(bbox);
|
|
31506
|
-
dragInfo = {
|
|
31507
|
-
id: e.id,
|
|
31508
|
-
target: target,
|
|
31509
|
-
ids: [],
|
|
31510
|
-
points: []
|
|
31511
|
-
};
|
|
31512
|
-
var iter = target.gui.displayArcs.getShapeIter(path);
|
|
31513
|
-
while (iter.hasNext()) {
|
|
31514
|
-
dragInfo.points.push([iter.x, iter.y]);
|
|
31515
|
-
dragInfo.ids.push(iter._arc.i);
|
|
31634
|
+
if (!dragInfo || dragInfo.id != e.id) {
|
|
31635
|
+
selectRectangle(e.id);
|
|
31516
31636
|
}
|
|
31517
|
-
gui.container.findChild('.map-layers').classed('dragging', true);
|
|
31518
|
-
|
|
31519
|
-
} else if (dragInfo) {
|
|
31520
|
-
gui.dispatchEvent('rectangle_dragend', dragInfo); // save undo state
|
|
31521
|
-
gui.container.findChild('.map-layers').classed('dragging', false);
|
|
31522
|
-
reset();
|
|
31523
31637
|
} else {
|
|
31524
|
-
|
|
31638
|
+
reset();
|
|
31525
31639
|
}
|
|
31526
|
-
|
|
31527
31640
|
});
|
|
31528
31641
|
|
|
31529
31642
|
box.on('handle_drag', function(e) {
|
|
31530
31643
|
if (!_on || !dragInfo) return;
|
|
31531
31644
|
var coords = internal.bboxToCoords(box.getDataCoords());
|
|
31532
31645
|
setRectangleCoords(dragInfo.target, dragInfo.ids, coords);
|
|
31646
|
+
dragInfo.moved = true;
|
|
31533
31647
|
gui.dispatchEvent('map-needs-refresh');
|
|
31534
31648
|
});
|
|
31535
31649
|
|
|
31650
|
+
// Each handle drag is one undo step, saved when it ends -- waiting until the
|
|
31651
|
+
// rectangle is deselected would lose the edit if the mode changes first.
|
|
31652
|
+
box.on('handle_up', function() {
|
|
31653
|
+
if (!_on || !dragInfo || !dragInfo.moved) return;
|
|
31654
|
+
gui.dispatchEvent('rectangle_dragend', {
|
|
31655
|
+
target: dragInfo.target,
|
|
31656
|
+
ids: dragInfo.ids,
|
|
31657
|
+
points: dragInfo.points
|
|
31658
|
+
});
|
|
31659
|
+
dragInfo.points = getDataPoints(dragInfo.target, dragInfo.ids);
|
|
31660
|
+
dragInfo.moved = false;
|
|
31661
|
+
});
|
|
31662
|
+
|
|
31663
|
+
function selectRectangle(id) {
|
|
31664
|
+
var target = hit.getHitTarget();
|
|
31665
|
+
var path = target.shapes[id][0];
|
|
31666
|
+
var bbox = target.gui.displayArcs.getSimpleShapeBounds(path).toArray();
|
|
31667
|
+
var ids = [];
|
|
31668
|
+
var iter = target.gui.displayArcs.getShapeIter(path);
|
|
31669
|
+
while (iter.hasNext()) {
|
|
31670
|
+
ids.push(iter._arc.i);
|
|
31671
|
+
}
|
|
31672
|
+
box.setDataCoords(bbox);
|
|
31673
|
+
dragInfo = {
|
|
31674
|
+
id: id,
|
|
31675
|
+
target: target,
|
|
31676
|
+
ids: ids,
|
|
31677
|
+
points: getDataPoints(target, ids), // for undo
|
|
31678
|
+
moved: false
|
|
31679
|
+
};
|
|
31680
|
+
gui.container.findChild('.map-layers').classed('dragging', true);
|
|
31681
|
+
}
|
|
31682
|
+
|
|
31683
|
+
function getDataPoints(target, ids) {
|
|
31684
|
+
return ids.map(function(id) {
|
|
31685
|
+
return getVertexCoords(target, id);
|
|
31686
|
+
});
|
|
31687
|
+
}
|
|
31688
|
+
|
|
31536
31689
|
function turnOn() {
|
|
31537
31690
|
box.turnOn();
|
|
31538
31691
|
_on = true;
|
|
@@ -31550,6 +31703,9 @@
|
|
|
31550
31703
|
|
|
31551
31704
|
function reset() {
|
|
31552
31705
|
box.hide();
|
|
31706
|
+
if (dragInfo) {
|
|
31707
|
+
gui.container.findChild('.map-layers').classed('dragging', false);
|
|
31708
|
+
}
|
|
31553
31709
|
dragInfo = null;
|
|
31554
31710
|
}
|
|
31555
31711
|
}
|
|
@@ -33613,22 +33769,20 @@
|
|
|
33613
33769
|
|
|
33614
33770
|
// Fitting obeys the same mode as a handle drag: holding the scale grows the
|
|
33615
33771
|
// output to cover the new extent, holding the output rescales onto it. The
|
|
33616
|
-
// command works out
|
|
33617
|
-
//
|
|
33772
|
+
// command works out the extent, which has to leave room for symbols and
|
|
33773
|
+
// labels at the scale it ends up at, along with the padding and the
|
|
33774
|
+
// held-scale width, so the margin's unit handling lives in one place.
|
|
33618
33775
|
function fitVisibleLayers() {
|
|
33619
33776
|
var target = getFrameTarget();
|
|
33620
33777
|
var entries = getCompositionEntries();
|
|
33621
33778
|
var margin = getMargin();
|
|
33622
|
-
var parts
|
|
33779
|
+
var parts;
|
|
33623
33780
|
if (!target || !entries.length) return;
|
|
33624
|
-
bounds = entries.reduce(function(memo, o) {
|
|
33625
|
-
return memo.mergeBounds(
|
|
33626
|
-
internal.getLayerBounds(o.layer, o.dataset.arcs)
|
|
33627
|
-
);
|
|
33628
|
-
}, new internal.Bounds());
|
|
33629
33781
|
parts = [
|
|
33630
33782
|
'-update-frame',
|
|
33631
|
-
'
|
|
33783
|
+
'fit=' + internal.formatOptionValue(entries.map(function(o) {
|
|
33784
|
+
return internal.getLayerTargetId(gui.model, o.layer);
|
|
33785
|
+
}).join(','))
|
|
33632
33786
|
];
|
|
33633
33787
|
if (margin) parts.push('offset=' + quoteCommandValue(margin));
|
|
33634
33788
|
if (!lockSize) parts.push('fix-scale');
|
|
@@ -33975,9 +34129,9 @@
|
|
|
33975
34129
|
revert: updateControls
|
|
33976
34130
|
});
|
|
33977
34131
|
// The width belongs to the same property as the colour and opacity beside
|
|
33978
|
-
// it
|
|
33979
|
-
//
|
|
33980
|
-
neatlineWidthInput =
|
|
34132
|
+
// it. The background row leaves the column empty rather than putting the
|
|
34133
|
+
// width on a line of its own.
|
|
34134
|
+
neatlineWidthInput = makeCellField(neatlineControl.aside, 'Width')
|
|
33981
34135
|
.addClass('frame-neatline-width')
|
|
33982
34136
|
.on('change', updateNeatlineWidth);
|
|
33983
34137
|
var clearRow = El('div')
|
|
@@ -34002,9 +34156,9 @@
|
|
|
34002
34156
|
unitsSelect.node().value = units;
|
|
34003
34157
|
aspectValue.text(getAspectText(frame));
|
|
34004
34158
|
backgroundControl.showColor(rec.fill || '');
|
|
34005
|
-
backgroundControl.opacity.node().value = formatOpacity(rec['fill-opacity']);
|
|
34159
|
+
backgroundControl.opacity.node().value = formatOpacity(rec['fill-opacity'], rec.fill);
|
|
34006
34160
|
neatlineControl.showColor(rec.stroke || '');
|
|
34007
|
-
neatlineControl.opacity.node().value = formatOpacity(rec['stroke-opacity']);
|
|
34161
|
+
neatlineControl.opacity.node().value = formatOpacity(rec['stroke-opacity'], rec.stroke);
|
|
34008
34162
|
neatlineWidthInput.node().value =
|
|
34009
34163
|
rec['stroke-width'] === undefined ? '' : rec['stroke-width'];
|
|
34010
34164
|
boundsValue.text(frame.bbox.map(formatCoordinate).join(', '));
|
|
@@ -34092,8 +34246,7 @@
|
|
|
34092
34246
|
}
|
|
34093
34247
|
|
|
34094
34248
|
// An extra captioned cell on an existing colour row.
|
|
34095
|
-
function
|
|
34096
|
-
var cell = El('div').addClass('label-split-cell').appendTo(row);
|
|
34249
|
+
function makeCellField(cell, label) {
|
|
34097
34250
|
El('span').appendTo(cell).text(label);
|
|
34098
34251
|
return makeTextInput(cell);
|
|
34099
34252
|
}
|
|
@@ -34131,9 +34284,13 @@
|
|
|
34131
34284
|
return String(Number(value.toPrecision(12)));
|
|
34132
34285
|
}
|
|
34133
34286
|
|
|
34134
|
-
|
|
34135
|
-
|
|
34136
|
-
|
|
34287
|
+
// A colour with no opacity of its own is drawn opaque, and says so; with no
|
|
34288
|
+
// colour either, there is nothing for an opacity to apply to.
|
|
34289
|
+
function formatOpacity(value, color) {
|
|
34290
|
+
if (value === undefined || value === null || value === '') {
|
|
34291
|
+
return color ? '100%' : '';
|
|
34292
|
+
}
|
|
34293
|
+
return String(Math.round(Number(value) * 100)) + '%';
|
|
34137
34294
|
}
|
|
34138
34295
|
|
|
34139
34296
|
// A fixed ratio holds when the frame is rescaled; one taken from the extent
|
|
@@ -35151,7 +35308,8 @@
|
|
|
35151
35308
|
}
|
|
35152
35309
|
|
|
35153
35310
|
function clampSidebarPanelsSeparatorPosition(pageY) {
|
|
35154
|
-
var
|
|
35311
|
+
var rect = gui.container.findChild('.sidebar-panels').node().getBoundingClientRect();
|
|
35312
|
+
var pct = 100 * (pageY - rect.top) / rect.height;
|
|
35155
35313
|
return Math.max(15, Math.min(85, pct));
|
|
35156
35314
|
}
|
|
35157
35315
|
}
|
|
@@ -35582,6 +35740,7 @@
|
|
|
35582
35740
|
new ImportControl(gui, importOpts);
|
|
35583
35741
|
new ExportControl(gui);
|
|
35584
35742
|
new LayerControl(gui);
|
|
35743
|
+
new SidebarTabs(gui);
|
|
35585
35744
|
new AddLayerLinks(gui);
|
|
35586
35745
|
HeaderMenu();
|
|
35587
35746
|
gui.console = new Console(gui);
|
|
@@ -35644,6 +35803,24 @@
|
|
|
35644
35803
|
El('body').addClass('map-view');
|
|
35645
35804
|
gui.console.runInitialCommands(getInitialConsoleCommands());
|
|
35646
35805
|
});
|
|
35806
|
+
|
|
35807
|
+
// The layer list, opened beside the first data so that the user sees what
|
|
35808
|
+
// was imported and where to go next -- once, and only for data: dismissing
|
|
35809
|
+
// the splash popup without importing anything leaves the sidebar closed.
|
|
35810
|
+
// Waits for any menu mode (the import dialog, while files are still loading)
|
|
35811
|
+
// to close, since opening a panel would close it.
|
|
35812
|
+
var layersShownForData = false;
|
|
35813
|
+
gui.model.on('update', showLayersForFirstData);
|
|
35814
|
+
gui.on('mode', showLayersForFirstData);
|
|
35815
|
+
function showLayersForFirstData() {
|
|
35816
|
+
var panels;
|
|
35817
|
+
if (layersShownForData || gui.model.isEmpty() || gui.getMode()) return;
|
|
35818
|
+
layersShownForData = true;
|
|
35819
|
+
panels = gui.getSidebarPanels();
|
|
35820
|
+
if (!panels.includes('layers')) {
|
|
35821
|
+
gui.setSidebarPanels(panels.concat('layers').sort());
|
|
35822
|
+
}
|
|
35823
|
+
}
|
|
35647
35824
|
};
|
|
35648
35825
|
|
|
35649
35826
|
})();
|