mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-15 21:48:20 +02:00
Enable scaling the selection via hotkeys
This commit is contained in:
@@ -30,6 +30,7 @@ export { actionRestrictTurn } from './restrict_turn';
|
||||
export { actionReverse } from './reverse';
|
||||
export { actionRevert } from './revert';
|
||||
export { actionRotate } from './rotate';
|
||||
export { actionScale } from './scale';
|
||||
export { actionSplit } from './split';
|
||||
export { actionStraightenNodes } from './straighten_nodes';
|
||||
export { actionStraightenWay } from './straighten_way';
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { utilGetAllNodes } from '../util';
|
||||
|
||||
export function actionScale(ids, pivotLoc, scaleFactor, projection) {
|
||||
return function(graph) {
|
||||
return graph.update(function(graph) {
|
||||
let point, radial;
|
||||
|
||||
utilGetAllNodes(ids, graph).forEach(function(node) {
|
||||
|
||||
point = projection(node.loc);
|
||||
radial = [
|
||||
point[0] - pivotLoc[0],
|
||||
point[1] - pivotLoc[1]
|
||||
];
|
||||
point = [
|
||||
pivotLoc[0] + (scaleFactor * radial[0]),
|
||||
pivotLoc[1] + (scaleFactor * radial[1])
|
||||
];
|
||||
|
||||
graph = graph.replace(node.move(projection.invert(point)));
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
+83
-2
@@ -5,6 +5,7 @@ import { t } from '../core/localizer';
|
||||
import { actionAddMidpoint } from '../actions/add_midpoint';
|
||||
import { actionDeleteRelation } from '../actions/delete_relation';
|
||||
import { actionMove } from '../actions/move';
|
||||
import { actionScale } from '../actions/scale';
|
||||
|
||||
import { behaviorBreathe } from '../behavior/breathe';
|
||||
import { behaviorHover } from '../behavior/hover';
|
||||
@@ -14,7 +15,7 @@ import { behaviorSelect } from '../behavior/select';
|
||||
|
||||
import { operationMove } from '../operations/move';
|
||||
|
||||
import { geoExtent, geoChooseEdge } from '../geo';
|
||||
import { geoExtent, geoChooseEdge, geoMetersToLat, geoMetersToLon } from '../geo';
|
||||
import { modeBrowse } from './browse';
|
||||
import { modeDragNode } from './drag_node';
|
||||
import { modeDragNote } from './drag_note';
|
||||
@@ -23,7 +24,7 @@ import * as Operations from '../operations/index';
|
||||
import { uiCmd } from '../ui/cmd';
|
||||
import {
|
||||
utilArrayIntersection, utilDeepMemberSelector, utilEntityOrDeepMemberSelector,
|
||||
utilEntitySelector, utilKeybinding
|
||||
utilEntitySelector, utilKeybinding, utilTotalExtent, utilGetAllNodes
|
||||
} from '../util';
|
||||
|
||||
|
||||
@@ -243,6 +244,10 @@ export function modeSelect(context, selectedIDs) {
|
||||
.on(uiCmd('⇧⌥↑'), nudgeSelection([0, -100]))
|
||||
.on(uiCmd('⇧⌥→'), nudgeSelection([100, 0]))
|
||||
.on(uiCmd('⇧⌥↓'), nudgeSelection([0, 100]))
|
||||
.on(utilKeybinding.plusKeys.map((key) => uiCmd('⇧' + key)), scaleSelection(1.05))
|
||||
.on(utilKeybinding.plusKeys.map((key) => uiCmd('⇧⌥' + key)), scaleSelection(Math.pow(1.05, 5)))
|
||||
.on(utilKeybinding.minusKeys.map((key) => uiCmd('⇧' + key)), scaleSelection(1/1.05))
|
||||
.on(utilKeybinding.minusKeys.map((key) => uiCmd('⇧⌥' + key)), scaleSelection(1/Math.pow(1.05, 5)))
|
||||
.on(['\\', 'pause'], nextParent)
|
||||
.on('⎋', esc, true);
|
||||
|
||||
@@ -303,6 +308,82 @@ export function modeSelect(context, selectedIDs) {
|
||||
.text(moveOp.tooltip)();
|
||||
} else {
|
||||
context.perform(actionMove(selectedIDs, delta, context.projection), moveOp.annotation());
|
||||
context.validator().validate();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function scaleSelection(factor) {
|
||||
return function() {
|
||||
// prevent scaling during low zoom selection
|
||||
if (!context.map().withinEditableZoom()) return;
|
||||
|
||||
let nodes = utilGetAllNodes(selectedIDs, context.graph());
|
||||
|
||||
let isUp = factor > 1;
|
||||
|
||||
// can only scale if multiple nodes are selected
|
||||
if (nodes.length <= 1) return;
|
||||
|
||||
let extent = utilTotalExtent(selectedIDs, context.graph());
|
||||
|
||||
// These disabled checks would normally be handled by an operation
|
||||
// object, but we don't want an actual scale operation at this point.
|
||||
function scalingDisabled() {
|
||||
|
||||
if (tooSmall()) {
|
||||
return 'too_small';
|
||||
} else if (extent.percentContainedIn(context.map().extent()) < 0.8) {
|
||||
return 'too_large';
|
||||
} else if (someMissing() || selectedIDs.some(incompleteRelation)) {
|
||||
return 'not_downloaded';
|
||||
} else if (selectedIDs.some(context.hasHiddenConnections)) {
|
||||
return 'connected_to_hidden';
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
function tooSmall() {
|
||||
if (isUp) return false;
|
||||
let dLon = Math.abs(extent[1][0] - extent[0][0]);
|
||||
let dLat = Math.abs(extent[1][1] - extent[0][1]);
|
||||
return dLon < geoMetersToLon(1, extent[1][1]) &&
|
||||
dLat < geoMetersToLat(1);
|
||||
}
|
||||
|
||||
function someMissing() {
|
||||
if (context.inIntro()) return false;
|
||||
let osm = context.connection();
|
||||
if (osm) {
|
||||
let missing = nodes.filter(function(n) { return !osm.isDataLoaded(n.loc); });
|
||||
if (missing.length) {
|
||||
missing.forEach(function(loc) { context.loadTileAtLoc(loc); });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function incompleteRelation(id) {
|
||||
let entity = context.entity(id);
|
||||
return entity.type === 'relation' && !entity.isComplete(context.graph());
|
||||
}
|
||||
}
|
||||
|
||||
const disabled = scalingDisabled();
|
||||
|
||||
if (disabled) {
|
||||
let multi = (selectedIDs.length === 1 ? 'single' : 'multiple');
|
||||
context.ui().flash
|
||||
.duration(4000)
|
||||
.iconName('#iD-icon-no')
|
||||
.iconClass('operation disabled')
|
||||
.text(t('operations.scale.' + disabled + '.' + multi))();
|
||||
} else {
|
||||
const pivot = context.projection(extent.center());
|
||||
const annotation = t('operations.scale.annotation.' + (isUp ? 'up' : 'down') + '.feature', { n: selectedIDs.length });
|
||||
context.perform(actionScale(selectedIDs, pivot, factor, context.projection), annotation);
|
||||
context.validator().validate();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,21 +35,25 @@ export function uiZoom(context) {
|
||||
}];
|
||||
|
||||
function zoomIn() {
|
||||
if (d3_event.shiftKey) return;
|
||||
d3_event.preventDefault();
|
||||
context.map().zoomIn();
|
||||
}
|
||||
|
||||
function zoomOut() {
|
||||
if (d3_event.shiftKey) return;
|
||||
d3_event.preventDefault();
|
||||
context.map().zoomOut();
|
||||
}
|
||||
|
||||
function zoomInFurther() {
|
||||
if (d3_event.shiftKey) return;
|
||||
d3_event.preventDefault();
|
||||
context.map().zoomInFurther();
|
||||
}
|
||||
|
||||
function zoomOutFurther() {
|
||||
if (d3_event.shiftKey) return;
|
||||
d3_event.preventDefault();
|
||||
context.map().zoomOutFurther();
|
||||
}
|
||||
|
||||
@@ -28,17 +28,22 @@ export function utilKeybinding(namespace) {
|
||||
if (matches(binding, true)) {
|
||||
binding.callback();
|
||||
didMatch = true;
|
||||
|
||||
// match a max of one binding per event
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// then unshifted keybindings
|
||||
if (didMatch) return;
|
||||
|
||||
// then unshifted keybindings
|
||||
for (i = 0; i < bindings.length; i++) {
|
||||
binding = bindings[i];
|
||||
if (binding.event.modifiers.shiftKey) continue; // shift
|
||||
if (!!binding.capture !== isCapturing) continue;
|
||||
if (matches(binding, false)) {
|
||||
binding.callback();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,8 +219,8 @@ utilKeybinding.modifierProperties = {
|
||||
91: 'metaKey'
|
||||
};
|
||||
|
||||
utilKeybinding.plusKeys = ['plus', 'ffplus', '=', 'ffequals'];
|
||||
utilKeybinding.minusKeys = ['_', '-', 'ffminus', 'dash'];
|
||||
utilKeybinding.plusKeys = ['plus', 'ffplus', '=', 'ffequals', '≠', '±'];
|
||||
utilKeybinding.minusKeys = ['_', '-', 'ffminus', 'dash', '–', '—'];
|
||||
|
||||
utilKeybinding.keys = {
|
||||
// Backspace key, on Mac: ⌫ (Backspace)
|
||||
|
||||
Reference in New Issue
Block a user