Merge branch 'defer-select-for-6028'

This commit is contained in:
Bryan Housel
2019-05-01 14:49:31 -04:00
3 changed files with 100 additions and 72 deletions
+47 -35
View File
@@ -1,25 +1,21 @@
import {
event as d3_event,
mouse as d3_mouse,
select as d3_select
} from 'd3-selection';
import { event as d3_event, mouse as d3_mouse, select as d3_select } from 'd3-selection';
import { geoVecLength } from '../geo';
import { modeBrowse } from '../modes/browse';
import { modeSelect } from '../modes/select';
import { modeSelectData } from '../modes/select_data';
import { modeSelectNote } from '../modes/select_note';
import { modeSelectError } from '../modes/select_error';
import { osmEntity, osmNote, qaError } from '../osm';
export function behaviorSelect(context) {
var lastMouse = null;
var suppressMenu = true;
// legacy option to show menu on every click
var isShowAlways = +context.storage('edit-menu-show-always') === 1;
var tolerance = 4;
var p1 = null;
var _lastMouse = null;
var _suppressMenu = true;
var _p1 = null;
function point() {
@@ -58,17 +54,20 @@ export function behaviorSelect(context) {
function mousedown() {
if (!p1) p1 = point();
if (!_p1) {
_p1 = point();
}
d3_select(window)
.on('mouseup.select', mouseup, true);
var isShowAlways = +context.storage('edit-menu-show-always') === 1;
suppressMenu = !isShowAlways;
_suppressMenu = !isShowAlways;
}
function mousemove() {
if (d3_event) lastMouse = d3_event;
if (d3_event) {
_lastMouse = d3_event;
}
}
@@ -83,15 +82,17 @@ export function behaviorSelect(context) {
e.stopPropagation();
if (!+e.clientX && !+e.clientY) {
if (lastMouse) {
e.sourceEvent = lastMouse;
if (_lastMouse) {
e.sourceEvent = _lastMouse;
} else {
return;
}
}
if (!p1) p1 = point();
suppressMenu = false;
if (!_p1) {
_p1 = point();
}
_suppressMenu = false;
click();
}
@@ -100,18 +101,27 @@ export function behaviorSelect(context) {
d3_select(window)
.on('mouseup.select', null, true);
if (!p1) return;
if (!_p1) return;
var p2 = point();
var dist = geoVecLength(p1, p2);
p1 = null;
if (dist > tolerance) {
return;
}
var dist = geoVecLength(_p1, p2);
_p1 = null;
if (dist > tolerance) return;
// Defer processing the click,
// because this click may trigger a blur event,
// and the blur event may trigger a tag change,
// and we really want that tag change to go to the already selected entity
// and not the one that we are about to select with the click #6028, #5878
// (Be very careful entering modeSelect anywhere that might also blur a field!)
var datum = d3_event.target.__data__ || (_lastMouse && _lastMouse.target.__data__);
var isMultiselect = d3_event.shiftKey || d3_select('#surface .lasso').node();
var isShowAlways = +context.storage('edit-menu-show-always') === 1;
var datum = d3_event.target.__data__ || (lastMouse && lastMouse.target.__data__);
window.setTimeout(function() {
processClick(datum, isMultiselect);
}, 20); // delay > whatever raw_tag_editor.js `scheduleChange` does (10ms).
}
function processClick(datum, isMultiselect) {
var mode = context.mode();
var entity = datum && datum.properties && datum.properties.entity;
@@ -127,18 +137,18 @@ export function behaviorSelect(context) {
context.selectedErrorID(null);
if (!isMultiselect) {
if (selectedIDs.length > 1 && (!suppressMenu && !isShowAlways)) {
if (selectedIDs.length > 1 && (!_suppressMenu && !isShowAlways)) {
// 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));
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 && !isShowAlways) {
if (!_suppressMenu && !isShowAlways) {
// don't deselect clicked entity, just show the menu.
mode.suppressMenu(false).reselect();
} else {
@@ -149,7 +159,7 @@ export function behaviorSelect(context) {
} else {
// clicked entity is not in the selected list, add it..
selectedIDs = selectedIDs.concat([datum.id]);
context.enter(modeSelect(context, selectedIDs).suppressMenu(suppressMenu));
context.enter(modeSelect(context, selectedIDs).suppressMenu(_suppressMenu));
}
}
@@ -162,10 +172,12 @@ export function behaviorSelect(context) {
context
.selectedNoteID(datum.id)
.enter(modeSelectNote(context, datum.id));
} else if (datum instanceof qaError & !isMultiselect) { // clicked an external QA error
context
.selectedErrorID(datum.id)
.enter(modeSelectError(context, datum.id, datum.service));
} else { // clicked nothing..
context.selectedNoteID(null);
context.selectedErrorID(null);
@@ -175,14 +187,14 @@ export function behaviorSelect(context) {
}
// reset for next time..
suppressMenu = true;
_suppressMenu = true;
}
function behavior(selection) {
lastMouse = null;
suppressMenu = true;
p1 = null;
_lastMouse = null;
_suppressMenu = true;
_p1 = null;
d3_select(window)
.on('keydown.select', keydown)
+23 -25
View File
@@ -40,7 +40,6 @@ export function modeSelect(context, selectedIDs) {
};
var keybinding = utilKeybinding('select');
var timeout = null;
var behaviors = [
behaviorCopy(context),
behaviorPaste(context),
@@ -51,11 +50,12 @@ export function modeSelect(context, selectedIDs) {
modeDragNode(context).restoreSelectedIDs(selectedIDs).behavior,
modeDragNote(context).behavior
];
var inspector;
var inspector; // unused?
var editMenu;
var newFeature = false;
var suppressMenu = true;
var follow = false;
var _timeout = null;
var _newFeature = false;
var _suppressMenu = true;
var _follow = false;
var wrap = context.container()
@@ -148,7 +148,7 @@ export function modeSelect(context, selectedIDs) {
var entity = singular();
if (entity && context.geometry(entity.id) === 'relation') {
suppressMenu = true;
_suppressMenu = true;
} else {
var point = context.mouse();
var viewport = geoExtent(context.projection.clipExtent()).polygon();
@@ -156,7 +156,7 @@ export function modeSelect(context, selectedIDs) {
if (point && geoPointInPolygon(point, viewport)) {
editMenu.center(point);
} else {
suppressMenu = true;
_suppressMenu = true;
}
}
}
@@ -203,29 +203,29 @@ export function modeSelect(context, selectedIDs) {
}
positionMenu();
if (!suppressMenu) {
if (!_suppressMenu) {
showMenu();
}
};
mode.newFeature = function(val) {
if (!arguments.length) return newFeature;
newFeature = val;
if (!arguments.length) return _newFeature;
_newFeature = val;
return mode;
};
mode.suppressMenu = function(val) {
if (!arguments.length) return suppressMenu;
suppressMenu = val;
if (!arguments.length) return _suppressMenu;
_suppressMenu = val;
return mode;
};
mode.follow = function(val) {
if (!arguments.length) return follow;
follow = val;
if (!arguments.length) return _follow;
_follow = val;
return mode;
};
@@ -280,7 +280,7 @@ export function modeSelect(context, selectedIDs) {
: uiEditMenu(context, operations);
context.ui().sidebar
.select(singular() ? singular().id : null, newFeature);
.select(singular() ? singular().id : null, _newFeature);
context.history()
.on('undone.select', update)
@@ -301,7 +301,7 @@ export function modeSelect(context, selectedIDs) {
context.ui().sidebar.show(entities);
}
if (follow) {
if (_follow) {
var extent = geoExtent();
var graph = context.graph();
selectedIDs.forEach(function(id) {
@@ -315,9 +315,9 @@ export function modeSelect(context, selectedIDs) {
context.map().pan([0,0]); // full redraw, to adjust z-sorting #2914
}
timeout = window.setTimeout(function() {
_timeout = window.setTimeout(function() {
positionMenu();
if (!suppressMenu) {
if (!_suppressMenu) {
showMenu();
}
}, 270); /* after any centerEase completes */
@@ -367,7 +367,7 @@ export function modeSelect(context, selectedIDs) {
var entity = singular();
if (entity && context.geometry(entity.id) === 'relation') {
suppressMenu = true;
_suppressMenu = true;
return;
}
@@ -516,7 +516,7 @@ export function modeSelect(context, selectedIDs) {
mode.exit = function() {
if (timeout) window.clearTimeout(timeout);
if (_timeout) window.clearTimeout(_timeout);
if (inspector) wrap.call(inspector.close);
behaviors.forEach(context.uninstall);
@@ -549,16 +549,14 @@ export function modeSelect(context, selectedIDs) {
context.features().forceVisible([]);
var entity = singular();
if (newFeature &&
entity &&
entity.type === 'relation' &&
if (_newFeature && entity && entity.type === 'relation' &&
// no tags
Object.keys(entity.tags).length === 0 &&
// no parent relations
context.graph().parentRelations(entity).length === 0 &&
// no members or one member with no role
(entity.members.length === 0 || (entity.members.length === 1 && !entity.members[0].role))) {
(entity.members.length === 0 || (entity.members.length === 1 && !entity.members[0].role))
) {
// the user added this relation but didn't edit it at all, so just delete it
var deleteAction = actionDeleteRelation(entity.id, true /* don't delete untagged members */);
context.perform(deleteAction, t('operations.delete.annotation.relation'));
+30 -12
View File
@@ -43,50 +43,68 @@ describe('iD.behaviorSelect', function() {
expect(context.mode().id).to.eql('browse');
});
specify('click on entity selects the entity', function() {
specify('click on entity selects the entity', function(done) {
var el = context.surface().selectAll('.' + a.id).node();
happen.mousedown(el);
happen.mouseup(el);
expect(context.selectedIDs()).to.eql([a.id]);
window.setTimeout(function() {
expect(context.selectedIDs()).to.eql([a.id]);
done();
}, 50);
});
specify('click on empty space clears the selection', function() {
specify('click on empty space clears the selection', function(done) {
context.enter(iD.modeSelect(context, [a.id]));
var el = context.surface().node();
happen.mousedown(el);
happen.mouseup(el);
expect(context.mode().id).to.eql('browse');
window.setTimeout(function() {
expect(context.mode().id).to.eql('browse');
done();
}, 50);
});
specify('shift-click on unselected entity adds it to the selection', function() {
specify('shift-click on unselected entity adds it to the selection', function(done) {
context.enter(iD.modeSelect(context, [a.id]));
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]);
window.setTimeout(function() {
expect(context.selectedIDs()).to.eql([a.id, b.id]);
done();
}, 50);
});
specify('shift-click on selected entity removes it from the selection', function() {
specify('shift-click on selected entity removes it from the selection', function(done) {
context.enter(iD.modeSelect(context, [a.id, b.id]));
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]);
window.setTimeout(function() {
expect(context.selectedIDs()).to.eql([a.id]);
done();
}, 50);
});
specify('shift-click on last selected entity clears the selection', function() {
specify('shift-click on last selected entity clears the selection', function(done) {
context.enter(iD.modeSelect(context, [a.id]));
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');
window.setTimeout(function() {
expect(context.mode().id).to.eql('browse');
done();
}, 50);
});
specify('shift-click on empty space leaves the selection unchanged', function() {
specify('shift-click on empty space leaves the selection unchanged', function(done) {
context.enter(iD.modeSelect(context, [a.id]));
var el = context.surface().node();
happen.mousedown(el, { shiftKey: true });
happen.mouseup(el, { shiftKey: true });
expect(context.selectedIDs()).to.eql([a.id]);
window.setTimeout(function() {
expect(context.selectedIDs()).to.eql([a.id]);
done();
}, 50);
});
});