mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 01:02:58 +00:00
More improvements to select behavior re contextmenu, shiftclick, etc.
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import * as d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import { modeBrowse, modeSelect } from '../modes/index';
|
||||
import { osmEntity } from '../osm/index';
|
||||
import { geoEuclideanDistance } from '../geo';
|
||||
import { modeBrowse, modeSelect } from '../modes';
|
||||
import { osmEntity } from '../osm';
|
||||
|
||||
|
||||
export function behaviorSelect(context) {
|
||||
var suppressMenu = true,
|
||||
tolerance = 4,
|
||||
p1 = null;
|
||||
|
||||
|
||||
function keydown() {
|
||||
if (d3.event && d3.event.shiftKey) {
|
||||
@@ -22,46 +27,92 @@ export function behaviorSelect(context) {
|
||||
}
|
||||
|
||||
|
||||
function click() {
|
||||
var rtClick = d3.event.type === 'contextmenu';
|
||||
function point() {
|
||||
return d3.mouse(context.container().node());
|
||||
}
|
||||
|
||||
if (rtClick) {
|
||||
d3.event.preventDefault();
|
||||
|
||||
function contextmenu() {
|
||||
if (!p1) p1 = point();
|
||||
d3.event.preventDefault();
|
||||
suppressMenu = false;
|
||||
click();
|
||||
}
|
||||
|
||||
|
||||
function mousedown() {
|
||||
if (!p1) p1 = point();
|
||||
d3.select(window)
|
||||
.on('mouseup.select', mouseup, true);
|
||||
}
|
||||
|
||||
|
||||
function mouseup() {
|
||||
click();
|
||||
}
|
||||
|
||||
|
||||
function click() {
|
||||
d3.select(window)
|
||||
.on('mouseup.select', null, true);
|
||||
|
||||
if (!p1) return;
|
||||
var p2 = point(),
|
||||
dist = geoEuclideanDistance(p1, p2);
|
||||
|
||||
p1 = null;
|
||||
if (dist > tolerance) {
|
||||
return;
|
||||
}
|
||||
|
||||
var datum = d3.event.target.__data__,
|
||||
lasso = d3.select('#surface .lasso').node(),
|
||||
isMultiselect = d3.event.shiftKey || d3.select('#surface .lasso').node(),
|
||||
mode = context.mode();
|
||||
|
||||
if (datum.type === 'midpoint') {
|
||||
// do nothing
|
||||
} else if (!(datum instanceof osmEntity)) {
|
||||
if (!d3.event.shiftKey && !lasso && mode.id !== 'browse')
|
||||
context.enter(modeBrowse(context));
|
||||
|
||||
} else if (!d3.event.shiftKey && !lasso) {
|
||||
// Reselect when 'rtClick on one of the selectedIDs'
|
||||
// OR 'leftClick on the same singular selected entity'
|
||||
// Explanation: leftClick should discard any multiple
|
||||
// selection of entities and make the selection singlular.
|
||||
// Whereas rtClick should preserve multiple selection of
|
||||
// entities if and only if it clicks on one of the selectedIDs.
|
||||
if (context.selectedIDs().indexOf(datum.id) >= 0 &&
|
||||
(rtClick || context.selectedIDs().length === 1)) {
|
||||
mode.suppressMenu(false).reselect();
|
||||
} else {
|
||||
context.enter(modeSelect(context, [datum.id]));
|
||||
}
|
||||
} else if (context.selectedIDs().indexOf(datum.id) >= 0) {
|
||||
if (rtClick) { // To prevent datum.id from being removed when rtClick
|
||||
mode.suppressMenu(false).reselect();
|
||||
} else {
|
||||
var selectedIDs = _.without(context.selectedIDs(), datum.id);
|
||||
context.enter(selectedIDs.length ? modeSelect(context, selectedIDs) : modeBrowse(context));
|
||||
if (datum.type === 'midpoint') {
|
||||
// clicked midpoint, do nothing..
|
||||
|
||||
} else if (!(datum instanceof osmEntity)) {
|
||||
// clicked nothing..
|
||||
if (!isMultiselect && mode.id !== 'browse') {
|
||||
context.enter(modeBrowse(context));
|
||||
}
|
||||
|
||||
} else {
|
||||
context.enter(modeSelect(context, context.selectedIDs().concat([datum.id])));
|
||||
// clicked an entity..
|
||||
var selectedIDs = context.selectedIDs();
|
||||
|
||||
if (!isMultiselect) {
|
||||
if (selectedIDs.length > 1 && !suppressMenu) {
|
||||
// multiple things already selected, just show the menu...
|
||||
mode.suppressMenu(false).reselect();
|
||||
} else {
|
||||
// select a single thing..
|
||||
context.enter(modeSelect(context, [datum.id]).suppressMenu(suppressMenu));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (selectedIDs.indexOf(datum.id) !== -1) {
|
||||
// clicked entity is already in the selectedIDs list..
|
||||
if (!suppressMenu) {
|
||||
// don't deselect clicked entity, just show the menu.
|
||||
mode.suppressMenu(false).reselect();
|
||||
} else {
|
||||
// deselect clicked entity, then reenter select mode or return to browse mode..
|
||||
selectedIDs = _.without(selectedIDs, datum.id);
|
||||
context.enter(selectedIDs.length ? modeSelect(context, selectedIDs) : modeBrowse(context));
|
||||
}
|
||||
} else {
|
||||
// clicked entity is not in the selected list, add it..
|
||||
selectedIDs = selectedIDs.concat([datum.id]);
|
||||
context.enter(modeSelect(context, selectedIDs).suppressMenu(suppressMenu));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reset for next time..
|
||||
suppressMenu = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,8 +121,9 @@ export function behaviorSelect(context) {
|
||||
.on('keydown.select', keydown)
|
||||
.on('keyup.select', keyup);
|
||||
|
||||
selection.on('click.select', click);
|
||||
selection.on('contextmenu.select', click);
|
||||
selection
|
||||
.on('mousedown.select', mousedown)
|
||||
.on('contextmenu.select', contextmenu);
|
||||
|
||||
keydown();
|
||||
};
|
||||
@@ -80,9 +132,12 @@ export function behaviorSelect(context) {
|
||||
behavior.off = function(selection) {
|
||||
d3.select(window)
|
||||
.on('keydown.select', null)
|
||||
.on('keyup.select', null);
|
||||
.on('keyup.select', null)
|
||||
.on('mouseup.select', null, true);
|
||||
|
||||
selection.on('click.select', null);
|
||||
selection
|
||||
.on('mousedown.select', null)
|
||||
.on('contextmenu.select', null);
|
||||
|
||||
keyup();
|
||||
};
|
||||
|
||||
@@ -193,7 +193,7 @@ export function modeSelect(context, selectedIDs) {
|
||||
}
|
||||
|
||||
positionMenu();
|
||||
if (d3.event && d3.event.type === 'contextmenu') {
|
||||
if (!suppressMenu) {
|
||||
showMenu();
|
||||
}
|
||||
};
|
||||
@@ -438,13 +438,15 @@ export function modeSelect(context, selectedIDs) {
|
||||
.on('move.select', closeMenu)
|
||||
.on('drawn.select', selectElements);
|
||||
|
||||
context.surface()
|
||||
.on('dblclick.select', dblclick);
|
||||
|
||||
|
||||
selectElements();
|
||||
|
||||
var show = d3.event;
|
||||
var rtClick = d3.event && d3.event.type === 'contextmenu';
|
||||
|
||||
if (show) {
|
||||
positionMenu();
|
||||
if (selectedIDs.length > 1) {
|
||||
var entities = uiSelectionList(context, selectedIDs);
|
||||
context.ui().sidebar.show(entities);
|
||||
}
|
||||
|
||||
if (follow) {
|
||||
@@ -460,18 +462,12 @@ export function modeSelect(context, selectedIDs) {
|
||||
}
|
||||
|
||||
timeout = window.setTimeout(function() {
|
||||
if (!suppressMenu && rtClick) {
|
||||
positionMenu();
|
||||
if (!suppressMenu) {
|
||||
showMenu();
|
||||
}
|
||||
}, 270); /* after any centerEase completes */
|
||||
|
||||
context.surface()
|
||||
.on('dblclick.select', dblclick);
|
||||
}, 200);
|
||||
|
||||
if (selectedIDs.length > 1) {
|
||||
var entities = uiSelectionList(context, selectedIDs);
|
||||
context.ui().sidebar.show(entities);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -44,37 +44,49 @@ describe('iD.behaviorSelect', function() {
|
||||
});
|
||||
|
||||
specify('click on entity selects the entity', function() {
|
||||
happen.click(context.surface().selectAll('.' + a.id).node());
|
||||
var el = context.surface().selectAll('.' + a.id).node();
|
||||
happen.mousedown(el);
|
||||
happen.mouseup(el);
|
||||
expect(context.selectedIDs()).to.eql([a.id]);
|
||||
});
|
||||
|
||||
specify('click on empty space clears the selection', function() {
|
||||
context.enter(iD.modeSelect(context, [a.id]));
|
||||
happen.click(context.surface().node());
|
||||
var el = context.surface().node();
|
||||
happen.mousedown(el);
|
||||
happen.mouseup(el);
|
||||
expect(context.mode().id).to.eql('browse');
|
||||
});
|
||||
|
||||
specify('shift-click on unselected entity adds it to the selection', function() {
|
||||
context.enter(iD.modeSelect(context, [a.id]));
|
||||
happen.click(context.surface().selectAll('.' + b.id).node(), {shiftKey: true});
|
||||
var el = context.surface().selectAll('.' + b.id).node();
|
||||
happen.mousedown(el, { shiftKey: true });
|
||||
happen.mouseup(el, { shiftKey: true });
|
||||
expect(context.selectedIDs()).to.eql([a.id, b.id]);
|
||||
});
|
||||
|
||||
specify('shift-click on selected entity removes it from the selection', function() {
|
||||
context.enter(iD.modeSelect(context, [a.id, b.id]));
|
||||
happen.click(context.surface().selectAll('.' + b.id).node(), {shiftKey: true});
|
||||
var el = context.surface().selectAll('.' + b.id).node();
|
||||
happen.mousedown(el, { shiftKey: true });
|
||||
happen.mouseup(el, { shiftKey: true });
|
||||
expect(context.selectedIDs()).to.eql([a.id]);
|
||||
});
|
||||
|
||||
specify('shift-click on last selected entity clears the selection', function() {
|
||||
context.enter(iD.modeSelect(context, [a.id]));
|
||||
happen.click(context.surface().selectAll('.' + a.id).node(), {shiftKey: true});
|
||||
var el = context.surface().selectAll('.' + a.id).node();
|
||||
happen.mousedown(el, { shiftKey: true });
|
||||
happen.mouseup(el, { shiftKey: true });
|
||||
expect(context.mode().id).to.eql('browse');
|
||||
});
|
||||
|
||||
specify('shift-click on empty space leaves the selection unchanged', function() {
|
||||
context.enter(iD.modeSelect(context, [a.id]));
|
||||
happen.click(context.surface().node(), {shiftKey: true});
|
||||
var el = context.surface().node();
|
||||
happen.mousedown(el, { shiftKey: true });
|
||||
happen.mouseup(el, { shiftKey: true });
|
||||
expect(context.selectedIDs()).to.eql([a.id]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user