mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-12 16:52:50 +00:00
Enable scaling the selection via hotkeys
This commit is contained in:
@@ -400,6 +400,28 @@ en:
|
||||
line: Reversed a line.
|
||||
lines: Reversed multiple lines.
|
||||
features: Reversed multiple features.
|
||||
scale:
|
||||
annotation:
|
||||
down:
|
||||
feature:
|
||||
one: Scaled down a feature.
|
||||
other: "Scaled down {n} features."
|
||||
up:
|
||||
feature:
|
||||
one: Scaled up a feature.
|
||||
other: "Scaled up {n} features."
|
||||
too_small:
|
||||
single: This feature can't be scaled down because it would become too small.
|
||||
multiple: These features can't be scaled down because they would become too small.
|
||||
too_large:
|
||||
single: This feature can't be scaled because not enough of it is currently visible.
|
||||
multiple: These features can't be scaled because not enough of them are currently visible.
|
||||
connected_to_hidden:
|
||||
single: This feature can't be scaled because it is connected to a hidden feature.
|
||||
multiple: These features can't be scaled because some are connected to hidden features.
|
||||
not_downloaded:
|
||||
single: This feature can't be scaled because parts of it have not yet been downloaded.
|
||||
multiple: These features can't be scaled because parts of them have not yet been downloaded.
|
||||
split:
|
||||
title: Split
|
||||
description:
|
||||
@@ -2172,6 +2194,8 @@ en:
|
||||
move: "Move selected features"
|
||||
nudge: "Nudge selected features"
|
||||
nudge_more: "Nudge selected features by a lot"
|
||||
scale: "Scale selected features"
|
||||
scale_more: "Scale selected features by a lot"
|
||||
rotate: "Rotate selected features"
|
||||
orthogonalize: "Square corners of a line or area"
|
||||
straighten: "Straighten a line or points"
|
||||
|
||||
@@ -281,6 +281,18 @@
|
||||
"text": "shortcuts.editing.operations.nudge_more",
|
||||
"separator": ","
|
||||
},
|
||||
{
|
||||
"modifiers": ["⇧"],
|
||||
"shortcuts": ["+", "-"],
|
||||
"text": "shortcuts.editing.operations.scale",
|
||||
"separator": ","
|
||||
},
|
||||
{
|
||||
"modifiers": ["⌥", "⇧"],
|
||||
"shortcuts": ["+", "-"],
|
||||
"text": "shortcuts.editing.operations.scale_more",
|
||||
"separator": ","
|
||||
},
|
||||
{
|
||||
"shortcuts": ["operations.rotate.key"],
|
||||
"text": "shortcuts.editing.operations.rotate"
|
||||
|
||||
34
dist/locales/en.json
vendored
34
dist/locales/en.json
vendored
@@ -527,6 +527,38 @@
|
||||
"features": "Reversed multiple features."
|
||||
}
|
||||
},
|
||||
"scale": {
|
||||
"annotation": {
|
||||
"down": {
|
||||
"feature": {
|
||||
"one": "Scaled down a feature.",
|
||||
"other": "Scaled down {n} features."
|
||||
}
|
||||
},
|
||||
"up": {
|
||||
"feature": {
|
||||
"one": "Scaled up a feature.",
|
||||
"other": "Scaled up {n} features."
|
||||
}
|
||||
}
|
||||
},
|
||||
"too_small": {
|
||||
"single": "This feature can't be scaled down because it would become too small.",
|
||||
"multiple": "These features can't be scaled down because they would become too small."
|
||||
},
|
||||
"too_large": {
|
||||
"single": "This feature can't be scaled because not enough of it is currently visible.",
|
||||
"multiple": "These features can't be scaled because not enough of them are currently visible."
|
||||
},
|
||||
"connected_to_hidden": {
|
||||
"single": "This feature can't be scaled because it is connected to a hidden feature.",
|
||||
"multiple": "These features can't be scaled because some are connected to hidden features."
|
||||
},
|
||||
"not_downloaded": {
|
||||
"single": "This feature can't be scaled because parts of it have not yet been downloaded.",
|
||||
"multiple": "These features can't be scaled because parts of them have not yet been downloaded."
|
||||
}
|
||||
},
|
||||
"split": {
|
||||
"title": "Split",
|
||||
"description": {
|
||||
@@ -2674,6 +2706,8 @@
|
||||
"move": "Move selected features",
|
||||
"nudge": "Nudge selected features",
|
||||
"nudge_more": "Nudge selected features by a lot",
|
||||
"scale": "Scale selected features",
|
||||
"scale_more": "Scale selected features by a lot",
|
||||
"rotate": "Rotate selected features",
|
||||
"orthogonalize": "Square corners of a line or area",
|
||||
"straighten": "Straighten a line or points",
|
||||
|
||||
@@ -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';
|
||||
|
||||
24
modules/actions/scale.js
Normal file
24
modules/actions/scale.js
Normal file
@@ -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)));
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -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