From 3102a4ae6ab61b9f0f805d9023626e3c2f2583cc Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Tue, 4 Oct 2016 19:06:20 -0400 Subject: [PATCH] Rename all the things to include pseudonamespaces --- data/index.js | 30 +-- modules/actions/add_entity.js | 2 +- modules/actions/add_member.js | 7 +- modules/actions/add_midpoint.js | 7 +- modules/actions/add_vertex.js | 2 +- modules/actions/change_member.js | 2 +- modules/actions/change_preset.js | 2 +- modules/actions/change_tags.js | 2 +- modules/actions/circularize.js | 22 ++- modules/actions/connect.js | 7 +- modules/actions/copy_entities.js | 5 +- modules/actions/delete_member.js | 7 +- modules/actions/delete_multiple.js | 18 +- modules/actions/delete_node.js | 13 +- modules/actions/delete_relation.js | 18 +- modules/actions/delete_way.js | 16 +- modules/actions/deprecate_tags.js | 12 +- modules/actions/discard_tags.js | 8 +- modules/actions/disconnect.js | 12 +- modules/actions/index.js | 66 +++---- modules/actions/join.js | 20 +- modules/actions/merge.js | 7 +- modules/actions/merge_polygon.js | 16 +- modules/actions/merge_remote_changes.js | 23 ++- modules/actions/move.js | 50 ++--- modules/actions/move_node.js | 2 +- modules/actions/noop.js | 2 +- modules/actions/orthogonalize.js | 21 +- modules/actions/restrict_turn.js | 20 +- modules/actions/reverse.js | 5 +- modules/actions/revert.js | 11 +- modules/actions/rotate_way.js | 3 +- modules/actions/split.js | 29 +-- modules/actions/straighten.js | 15 +- modules/actions/unrestrict_turn.js | 7 +- modules/behavior/add_way.js | 19 +- modules/behavior/breathe.js | 2 +- modules/behavior/copy.js | 13 +- modules/behavior/drag.js | 54 ++++-- modules/behavior/draw.js | 52 +++-- modules/behavior/draw_way.js | 116 +++++++---- modules/behavior/edit.js | 5 +- modules/behavior/hash.js | 21 +- modules/behavior/hover.js | 14 +- modules/behavior/index.js | 26 +-- modules/behavior/lasso.js | 22 ++- modules/behavior/paste.js | 43 +++-- modules/behavior/select.js | 25 ++- modules/behavior/tail.js | 71 ++++--- modules/core/connection.js | 88 ++++++--- modules/core/context.js | 55 +++--- modules/core/difference.js | 20 +- modules/core/entity.js | 66 ++++--- modules/core/graph.js | 37 +++- modules/core/history.js | 77 +++++--- modules/core/index.js | 22 +-- modules/core/node.js | 28 ++- modules/core/relation.js | 80 +++++--- modules/core/tags.js | 8 +- modules/core/tree.js | 15 +- modules/core/way.js | 55 ++++-- modules/geo/extent.js | 44 +++-- modules/geo/index.js | 88 +++++---- modules/geo/intersection.js | 33 ++-- modules/geo/multipolygon.js | 13 +- modules/geo/raw_mercator.js | 10 +- modules/index.js | 72 +++---- modules/lib/d3.combobox.js | 6 +- modules/modes/add_area.js | 69 ++++--- modules/modes/add_line.js | 60 +++--- modules/modes/add_point.js | 34 ++-- modules/modes/browse.js | 30 ++- modules/modes/drag_node.js | 111 +++++++---- modules/modes/draw_area.js | 10 +- modules/modes/draw_line.js | 10 +- modules/modes/index.js | 22 +-- modules/modes/move.js | 39 ++-- modules/modes/rotate_way.js | 81 ++++---- modules/modes/save.js | 76 +++++--- modules/modes/select.js | 105 ++++++---- modules/operations/circularize.js | 12 +- modules/operations/continue.js | 22 ++- modules/operations/delete.js | 34 ++-- modules/operations/disconnect.js | 14 +- modules/operations/index.js | 22 +-- modules/operations/merge.js | 39 ++-- modules/operations/move.js | 21 +- modules/operations/orthogonalize.js | 12 +- modules/operations/reverse.js | 15 +- modules/operations/rotate.js | 12 +- modules/operations/split.js | 19 +- modules/operations/straighten.js | 13 +- modules/presets/category.js | 17 +- modules/presets/collection.js | 21 +- modules/presets/field.js | 8 +- modules/presets/index.js | 10 +- modules/presets/{presets.js => init.js} | 37 ++-- modules/presets/preset.js | 14 +- modules/renderer/background.js | 46 +++-- modules/renderer/background_source.js | 36 +++- modules/renderer/features.js | 42 +++- modules/renderer/index.js | 10 +- modules/renderer/map.js | 85 ++++---- modules/renderer/tile_layer.js | 11 +- modules/services/index.js | 18 +- modules/services/mapillary.js | 38 ++-- modules/services/nominatim.js | 15 +- modules/services/taginfo.js | 29 ++- modules/services/wikidata.js | 5 +- modules/services/wikipedia.js | 9 +- modules/svg/areas.js | 21 +- modules/svg/debug.js | 22 +-- modules/svg/defs.js | 13 +- modules/svg/gpx.js | 25 +-- modules/svg/icon.js | 2 +- modules/svg/index.js | 40 ++-- modules/svg/labels.js | 34 ++-- modules/svg/layers.js | 32 +-- modules/svg/lines.js | 33 ++-- modules/svg/mapillary_images.js | 39 ++-- modules/svg/mapillary_signs.js | 38 ++-- modules/svg/midpoints.js | 32 ++- modules/svg/one_way_segments.js | 7 +- modules/svg/osm.js | 2 +- modules/svg/path.js | 2 +- modules/svg/point_transform.js | 2 +- modules/svg/points.js | 14 +- modules/svg/relation_member_tags.js | 2 +- modules/svg/tag_classes.js | 10 +- modules/svg/turns.js | 8 +- modules/svg/vertices.js | 11 +- modules/ui/account.js | 9 +- modules/ui/attribution.js | 3 +- modules/ui/background.js | 68 ++++--- modules/ui/cmd.js | 7 +- modules/ui/commit.js | 34 ++-- modules/ui/confirm.js | 9 +- modules/ui/conflicts.js | 25 ++- modules/ui/contributors.js | 9 +- modules/ui/disclosure.js | 39 ++-- modules/ui/entity_editor.js | 44 ++--- modules/ui/feature_info.js | 8 +- modules/ui/feature_list.js | 69 ++++--- modules/ui/fields/access.js | 21 +- modules/ui/fields/address.js | 43 +++-- modules/ui/fields/check.js | 12 +- modules/ui/fields/combo.js | 27 +-- modules/ui/fields/cycleway.js | 14 +- modules/ui/fields/index.js | 86 +++++---- modules/ui/fields/input.js | 33 ++-- modules/ui/fields/lanes.js | 11 +- modules/ui/fields/localized.js | 50 ++--- modules/ui/fields/maxspeed.js | 27 +-- modules/ui/fields/radio.js | 6 +- modules/ui/fields/restrictions.js | 95 +++++---- modules/ui/fields/textarea.js | 12 +- modules/ui/fields/wikipedia.js | 61 +++--- modules/ui/flash.js | 15 +- modules/ui/full_screen.js | 25 ++- modules/ui/geolocate.js | 30 +-- modules/ui/help.js | 20 +- modules/ui/index.js | 102 +++++----- modules/ui/info.js | 17 +- modules/ui/init.js | 235 +++++++++++++---------- modules/ui/inspector.js | 16 +- modules/ui/intro/area.js | 10 +- modules/ui/intro/index.js | 2 +- modules/ui/intro/intro.js | 35 ++-- modules/ui/intro/line.js | 20 +- modules/ui/intro/navigation.js | 6 +- modules/ui/intro/point.js | 10 +- modules/ui/intro/start_editing.js | 10 +- modules/ui/lasso.js | 30 +-- modules/ui/loading.js | 17 +- modules/ui/map_data.js | 165 ++++++++++------ modules/ui/map_in_map.js | 40 ++-- modules/ui/modal.js | 18 +- modules/ui/modes.js | 101 ++++++---- modules/ui/notice.js | 15 +- modules/ui/preset.js | 36 ++-- modules/ui/preset_icon.js | 20 +- modules/ui/preset_list.js | 62 +++--- modules/ui/radial_menu.js | 36 ++-- modules/ui/raw_member_editor.js | 58 +++--- modules/ui/raw_membership_editor.js | 78 +++++--- modules/ui/raw_tag_editor.js | 30 +-- modules/ui/restore.js | 26 ++- modules/ui/save.js | 69 ++++--- modules/ui/scale.js | 23 ++- modules/ui/selection_list.js | 22 +-- modules/ui/sidebar.js | 86 ++++++--- modules/ui/source_switch.js | 10 +- modules/ui/spinner.js | 21 +- modules/ui/splash.js | 31 +-- modules/ui/status.js | 12 +- modules/ui/success.js | 39 ++-- modules/ui/tag_reference.js | 13 +- modules/ui/toggle.js | 2 +- modules/ui/tooltipHtml.js | 2 +- modules/ui/undo_redo.js | 29 +-- modules/ui/view_on_osm.js | 14 +- modules/ui/zoom.js | 31 +-- modules/util/bind_once.js | 2 +- modules/util/curtain.js | 28 +-- modules/util/detect.js | 2 +- modules/util/dimensions.js | 6 +- modules/util/get_set_value.js | 2 +- modules/util/index.js | 42 ++-- modules/util/rebind.js | 2 +- modules/util/session_mutex.js | 2 +- modules/util/suggest_names.js | 6 +- modules/util/tooltip.js | 14 +- modules/util/trigger_event.js | 2 +- modules/util/util.js | 56 ++++-- modules/validations/deprecated_tag.js | 10 +- modules/validations/index.js | 8 +- modules/validations/many_deletions.js | 4 +- modules/validations/missing_tag.js | 5 +- modules/validations/tag_suggests_area.js | 7 +- 219 files changed, 3808 insertions(+), 2412 deletions(-) rename modules/presets/{presets.js => init.js} (80%) diff --git a/data/index.js b/data/index.js index 25ae238fc..863eb606f 100644 --- a/data/index.js +++ b/data/index.js @@ -1,24 +1,24 @@ -export { wikipedia } from 'wmf-sitematrix'; -export { default as featureIcons } from 'maki/www/maki-sprite.json'; -export { default as suggestions } from 'name-suggestion-index/name-suggestions.json'; +export { wikipedia as dataWikipedia } from 'wmf-sitematrix'; +export { default as dataFeatureIcons } from 'maki/www/maki-sprite.json'; +export { default as dataSuggestions } from 'name-suggestion-index/name-suggestions.json'; -export { default as deprecated } from './deprecated.json'; -export { default as discarded } from './discarded.json'; -export { default as imperial } from './imperial.json'; -export { default as locales } from './locales.json'; -export { default as addressFormats } from './address-formats.json'; -export { default as phoneFormats } from './phone-formats.json'; -export { default as driveLeft } from './drive-left.json'; -export { default as imagery } from './imagery.json'; -export { default as en } from '../dist/locales/en.json'; +export { default as dataDeprecated } from './deprecated.json'; +export { default as dataDiscarded } from './discarded.json'; +export { default as dataImperial } from './imperial.json'; +export { default as dataLocales } from './locales.json'; +export { default as dataAddressFormats } from './address-formats.json'; +export { default as dataPhoneFormats } from './phone-formats.json'; +export { default as dataDriveLeft } from './drive-left.json'; +export { default as dataImagery } from './imagery.json'; +export { default as dataEn } from '../dist/locales/en.json'; -import { default as presetsData } from './presets/presets.json'; +import { default as presets } from './presets/presets.json'; import { default as defaults } from './presets/defaults.json'; import { default as categories } from './presets/categories.json'; import { default as fields } from './presets/fields.json'; -export var presets = { - presets: presetsData, +export var dataPresets = { + presets: presets, defaults: defaults, categories: categories, fields: fields diff --git a/modules/actions/add_entity.js b/modules/actions/add_entity.js index 2a802693d..de2a4893d 100644 --- a/modules/actions/add_entity.js +++ b/modules/actions/add_entity.js @@ -1,4 +1,4 @@ -export function AddEntity(way) { +export function actionAddEntity(way) { return function(graph) { return graph.replace(way); }; diff --git a/modules/actions/add_member.js b/modules/actions/add_member.js index db9d58d21..0fa740858 100644 --- a/modules/actions/add_member.js +++ b/modules/actions/add_member.js @@ -1,6 +1,7 @@ -import { joinWays } from '../geo/index'; +import { geoJoinWays } from '../geo/index'; -export function AddMember(relationId, member, memberIndex) { + +export function actionAddMember(relationId, member, memberIndex) { return function(graph) { var relation = graph.entity(relationId); @@ -8,7 +9,7 @@ export function AddMember(relationId, member, memberIndex) { var members = relation.indexedMembers(); members.push(member); - var joined = joinWays(members, graph); + var joined = geoJoinWays(members, graph); for (var i = 0; i < joined.length; i++) { var segment = joined[i]; for (var j = 0; j < segment.length && segment.length >= 2; j++) { diff --git a/modules/actions/add_midpoint.js b/modules/actions/add_midpoint.js index b3a370617..30f8c8e15 100644 --- a/modules/actions/add_midpoint.js +++ b/modules/actions/add_midpoint.js @@ -1,7 +1,8 @@ import _ from 'lodash'; -import { edgeEqual } from '../geo/index'; +import { geoEdgeEqual } from '../geo/index'; -export function AddMidpoint(midpoint, node) { + +export function actionAddMidpoint(midpoint, node) { return function(graph) { graph = graph.replace(node.move(midpoint.loc)); @@ -11,7 +12,7 @@ export function AddMidpoint(midpoint, node) { parents.forEach(function(way) { for (var i = 0; i < way.nodes.length - 1; i++) { - if (edgeEqual([way.nodes[i], way.nodes[i + 1]], midpoint.edge)) { + if (geoEdgeEqual([way.nodes[i], way.nodes[i + 1]], midpoint.edge)) { graph = graph.replace(graph.entity(way.id).addNode(node.id, i + 1)); // Add only one midpoint on doubled-back segments, diff --git a/modules/actions/add_vertex.js b/modules/actions/add_vertex.js index 29473c286..7ee267566 100644 --- a/modules/actions/add_vertex.js +++ b/modules/actions/add_vertex.js @@ -1,5 +1,5 @@ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as -export function AddVertex(wayId, nodeId, index) { +export function actionAddVertex(wayId, nodeId, index) { return function(graph) { return graph.replace(graph.entity(wayId).addNode(nodeId, index)); }; diff --git a/modules/actions/change_member.js b/modules/actions/change_member.js index 5c2df07c2..896375765 100644 --- a/modules/actions/change_member.js +++ b/modules/actions/change_member.js @@ -1,4 +1,4 @@ -export function ChangeMember(relationId, member, memberIndex) { +export function actionChangeMember(relationId, member, memberIndex) { return function(graph) { return graph.replace(graph.entity(relationId).updateMember(member, memberIndex)); }; diff --git a/modules/actions/change_preset.js b/modules/actions/change_preset.js index d35278d3d..298142b64 100644 --- a/modules/actions/change_preset.js +++ b/modules/actions/change_preset.js @@ -1,4 +1,4 @@ -export function ChangePreset(entityId, oldPreset, newPreset) { +export function actionChangePreset(entityId, oldPreset, newPreset) { return function(graph) { var entity = graph.entity(entityId), geometry = entity.geometry(graph), diff --git a/modules/actions/change_tags.js b/modules/actions/change_tags.js index 703b5bdcb..304488de0 100644 --- a/modules/actions/change_tags.js +++ b/modules/actions/change_tags.js @@ -1,4 +1,4 @@ -export function ChangeTags(entityId, tags) { +export function actionChangeTags(entityId, tags) { return function(graph) { var entity = graph.entity(entityId); return graph.replace(entity.update({tags: tags})); diff --git a/modules/actions/circularize.js b/modules/actions/circularize.js index 8c8d5540b..70cd8544f 100644 --- a/modules/actions/circularize.js +++ b/modules/actions/circularize.js @@ -1,7 +1,7 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { euclideanDistance, interp } from '../geo/index'; -import { Node } from '../core/index'; +import { geoEuclideanDistance, geoInterp } from '../geo/index'; +import { coreNode } from '../core/index'; import { polygonArea as d3polygonArea, @@ -10,9 +10,10 @@ import { } from 'd3'; -export function Circularize(wayId, projection, maxAngle) { +export function actionCircularize(wayId, projection, maxAngle) { maxAngle = (maxAngle || 20) * Math.PI / 180; + var action = function(graph) { var way = graph.entity(wayId); @@ -24,8 +25,8 @@ export function Circularize(wayId, projection, maxAngle) { keyNodes = nodes.filter(function(n) { return graph.parentWays(n).length !== 1; }), points = nodes.map(function(n) { return projection(n.loc); }), keyPoints = keyNodes.map(function(n) { return projection(n.loc); }), - centroid = (points.length === 2) ? interp(points[0], points[1], 0.5) : d3polygonCentroid(points), - radius = d3.median(points, function(p) { return euclideanDistance(centroid, p); }), + centroid = (points.length === 2) ? geoInterp(points[0], points[1], 0.5) : d3polygonCentroid(points), + radius = d3.median(points, function(p) { return geoEuclideanDistance(centroid, p); }), sign = d3polygonArea(points) > 0 ? 1 : -1, ids; @@ -64,7 +65,7 @@ export function Circularize(wayId, projection, maxAngle) { } // position this key node - distance = euclideanDistance(centroid, keyPoints[i]); + distance = geoEuclideanDistance(centroid, keyPoints[i]); if (distance === 0) { distance = 1e-4; } keyPoints[i] = [ centroid[0] + (keyPoints[i][0] - centroid[0]) / distance * radius, @@ -104,7 +105,7 @@ export function Circularize(wayId, projection, maxAngle) { centroid[0] + Math.cos(angle) * radius, centroid[1] + Math.sin(angle) * radius]); - node = Node({loc: loc}); + node = coreNode({loc: loc}); graph = graph.replace(node); nodes.splice(endNodeIndex + j, 0, node); @@ -134,7 +135,7 @@ export function Circularize(wayId, projection, maxAngle) { insertAt = startIndex2; } for (j = 0; j < inBetweenNodes.length; j++) { - sharedWay = sharedWay.addNode(inBetweenNodes[j], insertAt + j); + sharedWay = sharedWay.addcoreNode(inBetweenNodes[j], insertAt + j); } graph = graph.replace(sharedWay); } @@ -154,6 +155,7 @@ export function Circularize(wayId, projection, maxAngle) { return graph; }; + action.makeConvex = function(graph) { var way = graph.entity(wayId), nodes = _.uniq(graph.childNodes(way)), @@ -178,7 +180,7 @@ export function Circularize(wayId, projection, maxAngle) { // move interior nodes to the surface of the convex hull.. for (var j = 1; j < indexRange; j++) { - var point = interp(hull[i], hull[i+1], j / indexRange), + var point = geoInterp(hull[i], hull[i+1], j / indexRange), node = nodes[(j + startIndex) % nodes.length].move(projection.invert(point)); graph = graph.replace(node); } @@ -186,10 +188,12 @@ export function Circularize(wayId, projection, maxAngle) { return graph; }; + action.disabled = function(graph) { if (!graph.entity(wayId).isClosed()) return 'not_closed'; }; + return action; } diff --git a/modules/actions/connect.js b/modules/actions/connect.js index 6437ff52d..03c0d609e 100644 --- a/modules/actions/connect.js +++ b/modules/actions/connect.js @@ -1,5 +1,6 @@ import _ from 'lodash'; -import { DeleteNode } from './delete_node'; +import { actionDeleteNode } from './delete_node'; + // Connect the ways at the given nodes. // @@ -15,7 +16,7 @@ import { DeleteNode } from './delete_node'; // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeNodesAction.as // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/MergeNodesAction.java // -export function Connect(nodeIds) { +export function actionConnect(nodeIds) { return function(graph) { var survivor = graph.entity(_.last(nodeIds)); @@ -35,7 +36,7 @@ export function Connect(nodeIds) { /* eslint-enable no-loop-func */ survivor = survivor.mergeTags(node.tags); - graph = DeleteNode(node.id)(graph); + graph = actionDeleteNode(node.id)(graph); } graph = graph.replace(survivor); diff --git a/modules/actions/copy_entities.js b/modules/actions/copy_entities.js index 1e87404b9..d47a0a3c7 100644 --- a/modules/actions/copy_entities.js +++ b/modules/actions/copy_entities.js @@ -1,6 +1,7 @@ -export function CopyEntities(ids, fromGraph) { +export function actionCopyEntities(ids, fromGraph) { var copies = {}; + var action = function(graph) { ids.forEach(function(id) { fromGraph.entity(id).copy(fromGraph, copies); @@ -13,9 +14,11 @@ export function CopyEntities(ids, fromGraph) { return graph; }; + action.copies = function() { return copies; }; + return action; } diff --git a/modules/actions/delete_member.js b/modules/actions/delete_member.js index a221615b7..0404508d1 100644 --- a/modules/actions/delete_member.js +++ b/modules/actions/delete_member.js @@ -1,6 +1,7 @@ -import { DeleteRelation } from './delete_relation'; +import { actionDeleteRelation } from './delete_relation'; -export function DeleteMember(relationId, memberIndex) { + +export function actionDeleteMember(relationId, memberIndex) { return function(graph) { var relation = graph.entity(relationId) .removeMember(memberIndex); @@ -8,7 +9,7 @@ export function DeleteMember(relationId, memberIndex) { graph = graph.replace(relation); if (relation.isDegenerate()) - graph = DeleteRelation(relation.id)(graph); + graph = actionDeleteRelation(relation.id)(graph); return graph; }; diff --git a/modules/actions/delete_multiple.js b/modules/actions/delete_multiple.js index 7eaf85519..db90d53ae 100644 --- a/modules/actions/delete_multiple.js +++ b/modules/actions/delete_multiple.js @@ -1,14 +1,16 @@ -import { DeleteNode } from './delete_node'; -import { DeleteRelation } from './delete_relation'; -import { DeleteWay } from './delete_way'; +import { actionDeleteNode } from './delete_node'; +import { actionDeleteRelation } from './delete_relation'; +import { actionDeleteWay } from './delete_way'; -export function DeleteMultiple(ids) { + +export function actionDeleteMultiple(ids) { var actions = { - way: DeleteWay, - node: DeleteNode, - relation: DeleteRelation + way: actionDeleteWay, + node: actionDeleteNode, + relation: actionDeleteRelation }; + var action = function(graph) { ids.forEach(function(id) { if (graph.hasEntity(id)) { // It may have been deleted aready. @@ -19,6 +21,7 @@ export function DeleteMultiple(ids) { return graph; }; + action.disabled = function(graph) { for (var i = 0; i < ids.length; i++) { var id = ids[i], @@ -27,5 +30,6 @@ export function DeleteMultiple(ids) { } }; + return action; } diff --git a/modules/actions/delete_node.js b/modules/actions/delete_node.js index 3097648e6..f8d9d99c3 100644 --- a/modules/actions/delete_node.js +++ b/modules/actions/delete_node.js @@ -1,8 +1,9 @@ -import { DeleteRelation } from './delete_relation'; -import { DeleteWay } from './delete_way'; +import { actionDeleteRelation } from './delete_relation'; +import { actionDeleteWay } from './delete_way'; + // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteNodeAction.as -export function DeleteNode(nodeId) { +export function actionDeleteNode(nodeId) { var action = function(graph) { var node = graph.entity(nodeId); @@ -12,7 +13,7 @@ export function DeleteNode(nodeId) { graph = graph.replace(parent); if (parent.isDegenerate()) { - graph = DeleteWay(parent.id)(graph); + graph = actionDeleteWay(parent.id)(graph); } }); @@ -22,16 +23,18 @@ export function DeleteNode(nodeId) { graph = graph.replace(parent); if (parent.isDegenerate()) { - graph = DeleteRelation(parent.id)(graph); + graph = actionDeleteRelation(parent.id)(graph); } }); return graph.remove(node); }; + action.disabled = function() { return false; }; + return action; } diff --git a/modules/actions/delete_relation.js b/modules/actions/delete_relation.js index ddf3e2d1f..cb74101a6 100644 --- a/modules/actions/delete_relation.js +++ b/modules/actions/delete_relation.js @@ -1,14 +1,18 @@ import _ from 'lodash'; -import { DeleteMultiple } from './delete_multiple'; +import { actionDeleteMultiple } from './delete_multiple'; + // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteRelationAction.as -export function DeleteRelation(relationId) { - function deleteEntity(entity, graph) { +export function actionDeleteRelation(relationId) { + + + function canDeleteEntity(entity, graph) { return !graph.parentWays(entity).length && !graph.parentRelations(entity).length && !entity.hasInterestingTags(); } + var action = function(graph) { var relation = graph.entity(relationId); @@ -18,7 +22,7 @@ export function DeleteRelation(relationId) { graph = graph.replace(parent); if (parent.isDegenerate()) { - graph = DeleteRelation(parent.id)(graph); + graph = actionDeleteRelation(parent.id)(graph); } }); @@ -26,18 +30,20 @@ export function DeleteRelation(relationId) { graph = graph.replace(relation.removeMembersWithID(memberId)); var entity = graph.entity(memberId); - if (deleteEntity(entity, graph)) { - graph = DeleteMultiple([memberId])(graph); + if (canDeleteEntity(entity, graph)) { + graph = actionDeleteMultiple([memberId])(graph); } }); return graph.remove(relation); }; + action.disabled = function(graph) { if (!graph.entity(relationId).isComplete(graph)) return 'incomplete_relation'; }; + return action; } diff --git a/modules/actions/delete_way.js b/modules/actions/delete_way.js index 61ff68b75..1917cd765 100644 --- a/modules/actions/delete_way.js +++ b/modules/actions/delete_way.js @@ -1,14 +1,18 @@ import _ from 'lodash'; -import { DeleteRelation } from './delete_relation'; +import { actionDeleteRelation } from './delete_relation'; + // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteWayAction.as -export function DeleteWay(wayId) { - function deleteNode(node, graph) { +export function actionDeleteWay(wayId) { + + + function canDeleteNode(node, graph) { return !graph.parentWays(node).length && !graph.parentRelations(node).length && !node.hasInterestingTags(); } + var action = function(graph) { var way = graph.entity(wayId); @@ -18,7 +22,7 @@ export function DeleteWay(wayId) { graph = graph.replace(parent); if (parent.isDegenerate()) { - graph = DeleteRelation(parent.id)(graph); + graph = actionDeleteRelation(parent.id)(graph); } }); @@ -26,7 +30,7 @@ export function DeleteWay(wayId) { graph = graph.replace(way.removeNode(nodeId)); var node = graph.entity(nodeId); - if (deleteNode(node, graph)) { + if (canDeleteNode(node, graph)) { graph = graph.remove(node); } }); @@ -34,6 +38,7 @@ export function DeleteWay(wayId) { return graph.remove(way); }; + action.disabled = function(graph) { var disabled = false; @@ -48,5 +53,6 @@ export function DeleteWay(wayId) { return disabled; }; + return action; } diff --git a/modules/actions/deprecate_tags.js b/modules/actions/deprecate_tags.js index f05ffc80c..e04f7ff7e 100644 --- a/modules/actions/deprecate_tags.js +++ b/modules/actions/deprecate_tags.js @@ -1,17 +1,19 @@ import _ from 'lodash'; -import { deprecated } from '../../data/index'; +import { dataDeprecated } from '../../data/index'; + + +export function actionDeprecateTags(entityId) { -export function DeprecateTags(entityId) { return function(graph) { var entity = graph.entity(entityId), newtags = _.clone(entity.tags), change = false, rule; - // This handles deprecated tags with a single condition - for (var i = 0; i < deprecated.length; i++) { + // This handles dataDeprecated tags with a single condition + for (var i = 0; i < dataDeprecated.length; i++) { - rule = deprecated[i]; + rule = dataDeprecated[i]; var match = _.toPairs(rule.old)[0], replacements = rule.replace ? _.toPairs(rule.replace) : null; diff --git a/modules/actions/discard_tags.js b/modules/actions/discard_tags.js index 76b6e93eb..ab34306de 100644 --- a/modules/actions/discard_tags.js +++ b/modules/actions/discard_tags.js @@ -1,7 +1,9 @@ import _ from 'lodash'; -import { discarded } from '../../data/index'; +import { dataDiscarded } from '../../data/index'; + + +export function actionDiscardTags(difference) { -export function DiscardTags(difference) { return function(graph) { function discardTags(entity) { if (!_.isEmpty(entity.tags)) { @@ -11,7 +13,7 @@ export function DiscardTags(difference) { }); graph = graph.replace(entity.update({ - tags: _.omit(tags, discarded) + tags: _.omit(tags, dataDiscarded) })); } } diff --git a/modules/actions/disconnect.js b/modules/actions/disconnect.js index f96fcf0f8..1c652f7f4 100644 --- a/modules/actions/disconnect.js +++ b/modules/actions/disconnect.js @@ -1,4 +1,5 @@ -import { Node } from '../core/index'; +import { coreNode } from '../core/index'; + // Disconect the ways at the given node. // @@ -14,16 +15,17 @@ import { Node } from '../core/index'; // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java // -export function Disconnect(nodeId, newNodeId) { +export function actionDisconnect(nodeId, newNodeId) { var wayIds; + var action = function(graph) { var node = graph.entity(nodeId), connections = action.connections(graph); connections.forEach(function(connection) { var way = graph.entity(connection.wayID), - newNode = Node({id: newNodeId, loc: node.loc, tags: node.tags}); + newNode = coreNode({id: newNodeId, loc: node.loc, tags: node.tags}); graph = graph.replace(newNode); if (connection.index === 0 && way.isArea()) { @@ -38,6 +40,7 @@ export function Disconnect(nodeId, newNodeId) { return graph; }; + action.connections = function(graph) { var candidates = [], keeping = false, @@ -62,6 +65,7 @@ export function Disconnect(nodeId, newNodeId) { return keeping ? candidates : candidates.slice(1); }; + action.disabled = function(graph) { var connections = action.connections(graph); if (connections.length === 0 || (wayIds && wayIds.length !== connections.length)) @@ -89,11 +93,13 @@ export function Disconnect(nodeId, newNodeId) { return 'relation'; }; + action.limitWays = function(_) { if (!arguments.length) return wayIds; wayIds = _; return action; }; + return action; } diff --git a/modules/actions/index.js b/modules/actions/index.js index 10a772d0e..afb314a94 100644 --- a/modules/actions/index.js +++ b/modules/actions/index.js @@ -1,33 +1,33 @@ -export { AddEntity } from './add_entity'; -export { AddMember } from './add_member'; -export { AddMidpoint } from './add_midpoint'; -export { AddVertex } from './add_vertex'; -export { ChangeMember } from './change_member'; -export { ChangePreset } from './change_preset'; -export { ChangeTags } from './change_tags'; -export { Circularize } from './circularize'; -export { Connect } from './connect'; -export { CopyEntities } from './copy_entities'; -export { DeleteMember } from './delete_member'; -export { DeleteMultiple } from './delete_multiple'; -export { DeleteNode } from './delete_node'; -export { DeleteRelation } from './delete_relation'; -export { DeleteWay } from './delete_way'; -export { DeprecateTags } from './deprecate_tags'; -export { DiscardTags } from './discard_tags'; -export { Disconnect } from './disconnect'; -export { Join } from './join'; -export { Merge } from './merge'; -export { MergePolygon } from './merge_polygon'; -export { MergeRemoteChanges } from './merge_remote_changes'; -export { Move } from './move'; -export { MoveNode } from './move_node'; -export { Noop } from './noop'; -export { Orthogonalize } from './orthogonalize'; -export { RestrictTurn } from './restrict_turn'; -export { Reverse } from './reverse'; -export { Revert } from './revert'; -export { RotateWay } from './rotate_way'; -export { Split } from './split'; -export { Straighten } from './straighten'; -export { UnrestrictTurn } from './unrestrict_turn'; +export { actionAddEntity } from './add_entity'; +export { actionAddMember } from './add_member'; +export { actionAddMidpoint } from './add_midpoint'; +export { actionAddVertex } from './add_vertex'; +export { actionChangeMember } from './change_member'; +export { actionChangePreset } from './change_preset'; +export { actionChangeTags } from './change_tags'; +export { actionCircularize } from './circularize'; +export { actionConnect } from './connect'; +export { actionCopyEntities } from './copy_entities'; +export { actionDeleteMember } from './delete_member'; +export { actionDeleteMultiple } from './delete_multiple'; +export { actionDeleteNode } from './delete_node'; +export { actionDeleteRelation } from './delete_relation'; +export { actionDeleteWay } from './delete_way'; +export { actionDeprecateTags } from './deprecate_tags'; +export { actionDiscardTags } from './discard_tags'; +export { actionDisconnect } from './disconnect'; +export { actionJoin } from './join'; +export { actionMerge } from './merge'; +export { actionMergePolygon } from './merge_polygon'; +export { actionMergeRemoteChanges } from './merge_remote_changes'; +export { actionMove } from './move'; +export { actionMoveNode } from './move_node'; +export { actionNoop } from './noop'; +export { actionOrthogonalize } from './orthogonalize'; +export { actionRestrictTurn } from './restrict_turn'; +export { actionReverse } from './reverse'; +export { actionRevert } from './revert'; +export { actionRotateWay } from './rotate_way'; +export { actionSplit } from './split'; +export { actionStraighten } from './straighten'; +export { actionUnrestrictTurn } from './unrestrict_turn'; diff --git a/modules/actions/join.js b/modules/actions/join.js index a9b0afaf5..994b45ea0 100644 --- a/modules/actions/join.js +++ b/modules/actions/join.js @@ -1,7 +1,8 @@ import _ from 'lodash'; -import { DeleteWay } from './delete_way'; -import { interestingTag } from '../core/index'; -import { joinWays } from '../geo/index'; +import { actionDeleteWay } from './delete_way'; +import { coreInterestingTag } from '../core/index'; +import { geoJoinWays } from '../geo/index'; + // Join ways at the end node they share. // @@ -11,13 +12,14 @@ import { joinWays } from '../geo/index'; // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeWaysAction.as // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/CombineWayAction.java // -export function Join(ids) { +export function actionJoin(ids) { function groupEntitiesByGeometry(graph) { var entities = ids.map(function(id) { return graph.entity(id); }); return _.extend({line: []}, _.groupBy(entities, function(entity) { return entity.geometry(graph); })); } + var action = function(graph) { var ways = ids.map(graph.entity, graph), survivor = ways[0]; @@ -30,7 +32,7 @@ export function Join(ids) { } } - var joined = joinWays(ways, graph)[0]; + var joined = geoJoinWays(ways, graph)[0]; survivor = survivor.update({nodes: _.map(joined.nodes, 'id')}); graph = graph.replace(survivor); @@ -46,18 +48,19 @@ export function Join(ids) { survivor = survivor.mergeTags(way.tags); graph = graph.replace(survivor); - graph = DeleteWay(way.id)(graph); + graph = actionDeleteWay(way.id)(graph); }); return graph; }; + action.disabled = function(graph) { var geometries = groupEntitiesByGeometry(graph); if (ids.length < 2 || ids.length !== geometries.line.length) return 'not_eligible'; - var joined = joinWays(ids.map(graph.entity, graph), graph); + var joined = geoJoinWays(ids.map(graph.entity, graph), graph); if (joined.length > 1) return 'not_adjacent'; @@ -76,7 +79,7 @@ export function Join(ids) { for (var k in way.tags) { if (!(k in tags)) { tags[k] = way.tags[k]; - } else if (tags[k] && interestingTag(k) && tags[k] !== way.tags[k]) { + } else if (tags[k] && coreInterestingTag(k) && tags[k] !== way.tags[k]) { conflicting = true; } } @@ -89,5 +92,6 @@ export function Join(ids) { return 'conflicting_tags'; }; + return action; } diff --git a/modules/actions/merge.js b/modules/actions/merge.js index 5aae60cfc..a2ff40ffc 100644 --- a/modules/actions/merge.js +++ b/modules/actions/merge.js @@ -1,12 +1,15 @@ import _ from 'lodash'; -export function Merge(ids) { + +export function actionMerge(ids) { + function groupEntitiesByGeometry(graph) { var entities = ids.map(function(id) { return graph.entity(id); }); return _.extend({point: [], area: [], line: [], relation: []}, _.groupBy(entities, function(entity) { return entity.geometry(graph); })); } + var action = function(graph) { var geometries = groupEntitiesByGeometry(graph), target = geometries.area[0] || geometries.line[0], @@ -27,6 +30,7 @@ export function Merge(ids) { return graph; }; + action.disabled = function(graph) { var geometries = groupEntitiesByGeometry(graph); if (geometries.point.length === 0 || @@ -35,5 +39,6 @@ export function Merge(ids) { return 'not_eligible'; }; + return action; } diff --git a/modules/actions/merge_polygon.js b/modules/actions/merge_polygon.js index d5c0aa0d1..1e162f60f 100644 --- a/modules/actions/merge_polygon.js +++ b/modules/actions/merge_polygon.js @@ -1,8 +1,9 @@ import _ from 'lodash'; -import { joinWays, polygonContainsPolygon } from '../geo/index'; -import { Relation } from '../core/index'; +import { geoJoinWays, geoPolygonContainsPolygon } from '../geo/index'; +import { coreRelation } from '../core/index'; -export function MergePolygon(ids, newRelationId) { + +export function actionMergePolygon(ids, newRelationId) { function groupEntities(graph) { var entities = ids.map(function (id) { return graph.entity(id); }); @@ -21,6 +22,7 @@ export function MergePolygon(ids, newRelationId) { })); } + var action = function(graph) { var entities = groupEntities(graph); @@ -29,7 +31,7 @@ export function MergePolygon(ids, newRelationId) { // Each element is itself an array of objects with an id property, and has a // locs property which is an array of the locations forming the polygon. var polygons = entities.multipolygon.reduce(function(polygons, m) { - return polygons.concat(joinWays(m.members, graph)); + return polygons.concat(geoJoinWays(m.members, graph)); }, []).concat(entities.closedWay.map(function(d) { var member = [{id: d.id}]; member.nodes = graph.childNodes(d); @@ -42,7 +44,7 @@ export function MergePolygon(ids, newRelationId) { var contained = polygons.map(function(w, i) { return polygons.map(function(d, n) { if (i === n) return null; - return polygonContainsPolygon( + return geoPolygonContainsPolygon( _.map(d.nodes, 'loc'), _.map(w.nodes, 'loc')); }); @@ -83,7 +85,7 @@ export function MergePolygon(ids, newRelationId) { // Move all tags to one relation var relation = entities.multipolygon[0] || - Relation({ id: newRelationId, tags: { type: 'multipolygon' }}); + coreRelation({ id: newRelationId, tags: { type: 'multipolygon' }}); entities.multipolygon.slice(1).forEach(function(m) { relation = relation.mergeTags(m.tags); @@ -106,6 +108,7 @@ export function MergePolygon(ids, newRelationId) { })); }; + action.disabled = function(graph) { var entities = groupEntities(graph); if (entities.other.length > 0 || @@ -115,5 +118,6 @@ export function MergePolygon(ids, newRelationId) { return 'incomplete_relation'; }; + return action; } diff --git a/modules/actions/merge_remote_changes.js b/modules/actions/merge_remote_changes.js index c130a06fc..34b0f3b2a 100644 --- a/modules/actions/merge_remote_changes.js +++ b/modules/actions/merge_remote_changes.js @@ -1,14 +1,16 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { DeleteMultiple } from './delete_multiple'; -import { Entity } from '../core/index'; +import { actionDeleteMultiple } from './delete_multiple'; +import { coreEntity } from '../core/index'; import { diff3_merge } from '../util/diff3'; -import { discarded } from '../../data/index'; +import { dataDiscarded } from '../../data/index'; -export function MergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { + +export function actionMergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { var option = 'safe', // 'safe', 'force_local', 'force_remote' conflicts = []; + function user(d) { return _.isFunction(formatUser) ? formatUser(d) : d; } @@ -101,14 +103,14 @@ export function MergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { updates.replacements.push(remote); } else if (option === 'force_local' && local) { - target = Entity(local); + target = coreEntity(local); if (remote) { target = target.update({ version: remote.version }); } updates.replacements.push(target); } else if (option === 'safe' && local && remote && local.version !== remote.version) { - target = Entity(local, { version: remote.version }); + target = coreEntity(local, { version: remote.version }); if (remote.visible) { target = mergeLocation(remote, target); } else { @@ -129,7 +131,7 @@ export function MergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { graph = graph.replace(updates.replacements[i]); } if (updates.removeIds.length) { - graph = DeleteMultiple(updates.removeIds)(graph); + graph = actionDeleteMultiple(updates.removeIds)(graph); } return graph; } @@ -150,7 +152,7 @@ export function MergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { function mergeTags(base, remote, target) { function ignoreKey(k) { - return _.includes(discarded, k); + return _.includes(dataDiscarded, k); } if (option === 'force_local' || _.isEqual(target.tags, remote.tags)) { @@ -206,7 +208,7 @@ export function MergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { base = graph.base().entities[id], local = localGraph.entity(id), remote = remoteGraph.entity(id), - target = Entity(local, { version: remote.version }); + target = coreEntity(local, { version: remote.version }); // delete/undelete if (!remote.visible) { @@ -249,14 +251,17 @@ export function MergeRemoteChanges(id, localGraph, remoteGraph, formatUser) { return graph; }; + action.withOption = function(opt) { option = opt; return action; }; + action.conflicts = function() { return conflicts; }; + return action; } diff --git a/modules/actions/move.js b/modules/actions/move.js index 23fa57755..e07f93e26 100644 --- a/modules/actions/move.js +++ b/modules/actions/move.js @@ -1,17 +1,18 @@ import _ from 'lodash'; -import { Node } from '../core/index'; +import { coreNode } from '../core/index'; import { - chooseEdge, - angle as getAngle, - interp, - pathIntersections, - pathLength, - sphericalDistance + geoChooseEdge, + geoAngle, + geoInterp, + geoPathIntersections, + geoPathLength, + geoSphericalDistance } from '../geo/index'; + // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/MoveCommand.java // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as -export function Move(moveIds, tryDelta, projection, cache) { +export function actionMove(moveIds, tryDelta, projection, cache) { var delta = tryDelta; function vecAdd(a, b) { return [a[0] + b[0], a[1] + b[1]]; } @@ -132,7 +133,7 @@ export function Move(moveIds, tryDelta, projection, cache) { var key = wayId + '_' + nodeId, orig = cache.replacedVertex[key]; if (!orig) { - orig = Node(); + orig = coreNode(); cache.replacedVertex[key] = orig; cache.startLoc[orig.id] = cache.startLoc[nodeId]; } @@ -146,21 +147,21 @@ export function Move(moveIds, tryDelta, projection, cache) { } orig = orig.move(end); - var angle = Math.abs(getAngle(orig, prev, projection) - - getAngle(orig, next, projection)) * 180 / Math.PI; + var angle = Math.abs(geoAngle(orig, prev, projection) - + geoAngle(orig, next, projection)) * 180 / Math.PI; // Don't add orig vertex if it would just make a straight line.. if (angle > 175 && angle < 185) return graph; // Don't add orig vertex if another point is already nearby (within 10m) - if (sphericalDistance(prev.loc, orig.loc) < 10 || - sphericalDistance(orig.loc, next.loc) < 10) return graph; + if (geoSphericalDistance(prev.loc, orig.loc) < 10 || + geoSphericalDistance(orig.loc, next.loc) < 10) return graph; // moving forward or backward along way? var p1 = [prev.loc, orig.loc, moved.loc, next.loc].map(projection), p2 = [prev.loc, moved.loc, orig.loc, next.loc].map(projection), - d1 = pathLength(p1), - d2 = pathLength(p2), + d1 = geoPathLength(p1), + d2 = geoPathLength(p2), insertAt = (d1 < d2) ? movedIndex : nextIndex; // moving around closed loop? @@ -170,6 +171,7 @@ export function Move(moveIds, tryDelta, projection, cache) { return graph.replace(orig).replace(way); } + // Reorder nodes around intersections that have moved.. function unZorroIntersection(intersection, graph) { var vertex = graph.entity(intersection.nodeId), @@ -187,17 +189,17 @@ export function Move(moveIds, tryDelta, projection, cache) { if (way1.isClosed() && way1.first() === vertex.id) nodes1.push(nodes1[0]); if (way2.isClosed() && way2.first() === vertex.id) nodes2.push(nodes2[0]); - var edge1 = !isEP1 && chooseEdge(nodes1, projection(vertex.loc), projection), - edge2 = !isEP2 && chooseEdge(nodes2, projection(vertex.loc), projection), + var edge1 = !isEP1 && geoChooseEdge(nodes1, projection(vertex.loc), projection), + edge2 = !isEP2 && geoChooseEdge(nodes2, projection(vertex.loc), projection), loc; // snap vertex to nearest edge (or some point between them).. if (!isEP1 && !isEP2) { var epsilon = 1e-4, maxIter = 10; for (var i = 0; i < maxIter; i++) { - loc = interp(edge1.loc, edge2.loc, 0.5); - edge1 = chooseEdge(nodes1, projection(loc), projection); - edge2 = chooseEdge(nodes2, projection(loc), projection); + loc = geoInterp(edge1.loc, edge2.loc, 0.5); + edge1 = geoChooseEdge(nodes1, projection(loc), projection); + edge2 = geoChooseEdge(nodes2, projection(loc), projection); if (Math.abs(edge1.distance - edge2.distance) < epsilon) break; } } else if (!isEP1) { @@ -232,6 +234,7 @@ export function Move(moveIds, tryDelta, projection, cache) { return graph; } + // check if moving way endpoint can cross an unmoved way, if so limit delta.. function limitDelta(graph) { _.each(cache.intersection, function(obj) { @@ -248,11 +251,11 @@ export function Move(moveIds, tryDelta, projection, cache) { function(loc) { return vecAdd(projection(loc), delta); }), unmovedNodes = graph.childNodes(graph.entity(obj.unmovedId)), unmovedPath = _.map(_.map(unmovedNodes, 'loc'), projection), - hits = pathIntersections(movedPath, unmovedPath); + hits = geoPathIntersections(movedPath, unmovedPath); for (var i = 0; i < hits.length; i++) { if (_.isEqual(hits[i], end)) continue; - var edge = chooseEdge(unmovedNodes, end, projection); + var edge = geoChooseEdge(unmovedNodes, end, projection); delta = vecSub(projection(edge.loc), start); } }); @@ -282,6 +285,7 @@ export function Move(moveIds, tryDelta, projection, cache) { return graph; }; + action.disabled = function(graph) { function incompleteRelation(id) { var entity = graph.entity(id); @@ -292,9 +296,11 @@ export function Move(moveIds, tryDelta, projection, cache) { return 'incomplete_relation'; }; + action.delta = function() { return delta; }; + return action; } diff --git a/modules/actions/move_node.js b/modules/actions/move_node.js index 0fa702d02..6e2593b45 100644 --- a/modules/actions/move_node.js +++ b/modules/actions/move_node.js @@ -1,6 +1,6 @@ // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/MoveCommand.java // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as -export function MoveNode(nodeId, loc) { +export function actionMoveNode(nodeId, loc) { return function(graph) { return graph.replace(graph.entity(nodeId).move(loc)); }; diff --git a/modules/actions/noop.js b/modules/actions/noop.js index 573cd535b..996323e13 100644 --- a/modules/actions/noop.js +++ b/modules/actions/noop.js @@ -1,4 +1,4 @@ -export function Noop() { +export function actionNoop() { return function(graph) { return graph; }; diff --git a/modules/actions/orthogonalize.js b/modules/actions/orthogonalize.js index 983c8dfc0..b3c311b33 100644 --- a/modules/actions/orthogonalize.js +++ b/modules/actions/orthogonalize.js @@ -1,15 +1,16 @@ import _ from 'lodash'; -import { DeleteNode } from './delete_node'; -import { euclideanDistance } from '../geo/index'; +import { actionDeleteNode } from './delete_node'; +import { geoEuclideanDistance } from '../geo/index'; /* * Based on https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/potlatch2/tools/Quadrilateralise.as */ -export function Orthogonalize(wayId, projection) { +export function actionOrthogonalize(wayId, projection) { var threshold = 12, // degrees within right or straight to alter lowerThreshold = Math.cos((90 - threshold) * Math.PI / 180), upperThreshold = Math.cos(threshold * Math.PI / 180); + var action = function(graph) { var way = graph.entity(wayId), nodes = graph.childNodes(way), @@ -30,6 +31,7 @@ export function Orthogonalize(wayId, projection) { graph = graph.replace(graph.entity(nodes[corner.i].id) .move(projection.invert(points[corner.i]))); + } else { var best, originalPoints = _.clone(points); @@ -73,13 +75,14 @@ export function Orthogonalize(wayId, projection) { var dotp = normalizedDotProduct(i, points); if (dotp < -1 + epsilon) { - graph = DeleteNode(nodes[i].id)(graph); + graph = actionDeleteNode(nodes[i].id)(graph); } } } return graph; + function calcMotion(b, i, array) { var a = array[(i - 1 + array.length) % array.length], c = array[(i + 1) % array.length], @@ -87,7 +90,7 @@ export function Orthogonalize(wayId, projection) { q = subtractPoints(c, b), scale, dotp; - scale = 2 * Math.min(euclideanDistance(p, [0, 0]), euclideanDistance(q, [0, 0])); + scale = 2 * Math.min(geoEuclideanDistance(p, [0, 0]), geoEuclideanDistance(q, [0, 0])); p = normalizePoint(p, 1.0); q = normalizePoint(q, 1.0); @@ -107,6 +110,7 @@ export function Orthogonalize(wayId, projection) { } }; + function squareness(points) { return points.reduce(function(sum, val, i, array) { var dotp = normalizedDotProduct(i, array); @@ -116,6 +120,7 @@ export function Orthogonalize(wayId, projection) { }, 0); } + function normalizedDotProduct(i, points) { var a = points[(i - 1 + points.length) % points.length], b = points[i], @@ -129,14 +134,17 @@ export function Orthogonalize(wayId, projection) { return p[0] * q[0] + p[1] * q[1]; } + function subtractPoints(a, b) { return [a[0] - b[0], a[1] - b[1]]; } + function addPoints(a, b) { return [a[0] + b[0], a[1] + b[1]]; } + function normalizePoint(point, scale) { var vector = [0, 0]; var length = Math.sqrt(point[0] * point[0] + point[1] * point[1]); @@ -151,6 +159,7 @@ export function Orthogonalize(wayId, projection) { return vector; } + function filterDotProduct(dotp) { if (lowerThreshold > Math.abs(dotp) || Math.abs(dotp) > upperThreshold) { return dotp; @@ -159,6 +168,7 @@ export function Orthogonalize(wayId, projection) { return 0; } + action.disabled = function(graph) { var way = graph.entity(wayId), nodes = graph.childNodes(way), @@ -171,5 +181,6 @@ export function Orthogonalize(wayId, projection) { return 'not_squarish'; }; + return action; } diff --git a/modules/actions/restrict_turn.js b/modules/actions/restrict_turn.js index 0afc7c68a..88644b05d 100644 --- a/modules/actions/restrict_turn.js +++ b/modules/actions/restrict_turn.js @@ -1,6 +1,7 @@ -import { Relation, Way } from '../core/index'; -import { Split } from './split'; -import { inferRestriction } from '../geo/index'; +import { coreRelation, coreWay } from '../core/index'; +import { actionSplit } from './split'; +import { geoInferRestriction } from '../geo/index'; + // Create a restriction relation for `turn`, which must have the following structure: // @@ -16,7 +17,7 @@ import { inferRestriction } from '../geo/index'; // (The action does not check that these entities form a valid intersection.) // // If `restriction` is not provided, it is automatically determined by -// inferRestriction. +// geoInferRestriction. // // If necessary, the `from` and `to` ways are split. In these cases, `from.node` // and `to.node` are used to determine which portion of the split ways become @@ -26,7 +27,8 @@ import { inferRestriction } from '../geo/index'; // Normally, this will be undefined and the relation will automatically // be assigned a new ID. // -export function RestrictTurn(turn, projection, restrictionId) { +export function actionRestrictTurn(turn, projection, restrictionId) { + return function(graph) { var from = graph.entity(turn.from.way), via = graph.entity(turn.via.node), @@ -37,8 +39,8 @@ export function RestrictTurn(turn, projection, restrictionId) { } function split(toOrFrom) { - var newID = toOrFrom.newID || Way().id; - graph = Split(via.id, [newID]) + var newID = toOrFrom.newID || coreWay().id; + graph = actionSplit(via.id, [newID]) .limitWays([toOrFrom.way])(graph); var a = graph.entity(newID), @@ -70,12 +72,12 @@ export function RestrictTurn(turn, projection, restrictionId) { to = split(turn.to)[0]; } - return graph.replace(Relation({ + return graph.replace(coreRelation({ id: restrictionId, tags: { type: 'restriction', restriction: turn.restriction || - inferRestriction( + geoInferRestriction( graph, turn.from, turn.via, diff --git a/modules/actions/reverse.js b/modules/actions/reverse.js index 1fb18ad85..cd1eda959 100644 --- a/modules/actions/reverse.js +++ b/modules/actions/reverse.js @@ -29,7 +29,7 @@ http://wiki.openstreetmap.org/wiki/Route#Members http://josm.openstreetmap.de/browser/josm/trunk/src/org/openstreetmap/josm/corrector/ReverseWayTagCorrector.java */ -export function Reverse(wayId, options) { +export function actionReverse(wayId, options) { var replacements = [ [/:right$/, ':left'], [/:left$/, ':right'], [/:forward$/, ':backward'], [/:backward$/, ':forward'] @@ -44,6 +44,7 @@ export function Reverse(wayId, options) { west: 'east' }; + function reverseKey(key) { for (var i = 0; i < replacements.length; ++i) { var replacement = replacements[i]; @@ -54,6 +55,7 @@ export function Reverse(wayId, options) { return key; } + function reverseValue(key, value) { if (key === 'incline' && numeric.test(value)) { return value.replace(numeric, function(_, sign) { return sign === '-' ? '' : '-'; }); @@ -66,6 +68,7 @@ export function Reverse(wayId, options) { } } + return function(graph) { var way = graph.entity(wayId), nodes = way.nodes.slice().reverse(), diff --git a/modules/actions/revert.js b/modules/actions/revert.js index 1580bd595..018fb709e 100644 --- a/modules/actions/revert.js +++ b/modules/actions/revert.js @@ -1,7 +1,8 @@ -import { DeleteRelation } from './delete_relation'; -import { DeleteWay } from './delete_way'; +import { actionDeleteRelation } from './delete_relation'; +import { actionDeleteWay } from './delete_way'; -export function Revert(id) { + +export function actionRevert(id) { var action = function(graph) { var entity = graph.hasEntity(id), base = graph.base().entities[id]; @@ -14,7 +15,7 @@ export function Revert(id) { graph = graph.replace(parent); if (parent.isDegenerate()) { - graph = DeleteWay(parent.id)(graph); + graph = actionDeleteWay(parent.id)(graph); } }); } @@ -25,7 +26,7 @@ export function Revert(id) { graph = graph.replace(parent); if (parent.isDegenerate()) { - graph = DeleteRelation(parent.id)(graph); + graph = actionDeleteRelation(parent.id)(graph); } }); } diff --git a/modules/actions/rotate_way.js b/modules/actions/rotate_way.js index 539d73714..6f259da22 100644 --- a/modules/actions/rotate_way.js +++ b/modules/actions/rotate_way.js @@ -1,6 +1,7 @@ import _ from 'lodash'; -export function RotateWay(wayId, pivot, angle, projection) { + +export function actionRotateWay(wayId, pivot, angle, projection) { return function(graph) { return graph.update(function(graph) { var way = graph.entity(wayId); diff --git a/modules/actions/split.js b/modules/actions/split.js index 87f8b9f37..0ec0d2831 100644 --- a/modules/actions/split.js +++ b/modules/actions/split.js @@ -1,8 +1,9 @@ import _ from 'lodash'; -import { Relation, Way } from '../core/index'; -import { isSimpleMultipolygonOuterMember, sphericalDistance } from '../geo/index'; -import { AddMember } from './add_member'; -import { wrap as Wrap } from '../util/index'; +import { coreRelation, coreWay } from '../core/index'; +import { geoIsSimpleMultipolygonOuterMember, geoSphericalDistance } from '../geo/index'; +import { actionAddMember } from './add_member'; +import { utilWrap } from '../util/index'; + // Split a way at the given node. // @@ -18,7 +19,7 @@ import { wrap as Wrap } from '../util/index'; // Reference: // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as // -export function Split(nodeId, newWayIds) { +export function actionSplit(nodeId, newWayIds) { var wayIds; // if the way is closed, we need to search for a partner node @@ -39,11 +40,11 @@ export function Split(nodeId, newWayIds) { idxB; function wrap(index) { - return Wrap(index, nodes.length); + return utilWrap(index, nodes.length); } function dist(nA, nB) { - return sphericalDistance(graph.entity(nA).loc, graph.entity(nB).loc); + return geoSphericalDistance(graph.entity(nA).loc, graph.entity(nB).loc); } // calculate lengths @@ -72,12 +73,13 @@ export function Split(nodeId, newWayIds) { return idxB; } + function split(graph, wayA, newWayId) { - var wayB = Way({id: newWayId, tags: wayA.tags}), + var wayB = coreWay({id: newWayId, tags: wayA.tags}), nodesA, nodesB, isArea = wayA.isArea(), - isOuter = isSimpleMultipolygonOuterMember(wayA, graph); + isOuter = geoIsSimpleMultipolygonOuterMember(wayA, graph); if (wayA.isClosed()) { var nodes = wayA.nodes.slice(0, -1), @@ -123,12 +125,12 @@ export function Split(nodeId, newWayIds) { role: relation.memberById(wayA.id).role }; - graph = AddMember(relation.id, member)(graph); + graph = actionAddMember(relation.id, member)(graph); } }); if (!isOuter && isArea) { - var multipolygon = Relation({ + var multipolygon = coreRelation({ tags: _.extend({}, wayA.tags, {type: 'multipolygon'}), members: [ {id: wayA.id, role: 'outer', type: 'way'}, @@ -143,6 +145,7 @@ export function Split(nodeId, newWayIds) { return graph; } + var action = function(graph) { var candidates = action.ways(graph); for (var i = 0; i < candidates.length; i++) { @@ -151,6 +154,7 @@ export function Split(nodeId, newWayIds) { return graph; }; + action.ways = function(graph) { var node = graph.entity(nodeId), parents = graph.parentWays(node), @@ -177,17 +181,20 @@ export function Split(nodeId, newWayIds) { }); }; + action.disabled = function(graph) { var candidates = action.ways(graph); if (candidates.length === 0 || (wayIds && wayIds.length !== candidates.length)) return 'not_eligible'; }; + action.limitWays = function(_) { if (!arguments.length) return wayIds; wayIds = _; return action; }; + return action; } diff --git a/modules/actions/straighten.js b/modules/actions/straighten.js index a66ec0a24..c7de86011 100644 --- a/modules/actions/straighten.js +++ b/modules/actions/straighten.js @@ -1,14 +1,17 @@ -import { DeleteNode } from './delete_node'; +import { actionDeleteNode } from './delete_node'; + /* * Based on https://github.com/openstreetmap/potlatch2/net/systemeD/potlatch2/tools/Straighten.as */ -export function Straighten(wayId, projection) { +export function actionStraighten(wayId, projection) { + function positionAlongWay(n, s, e) { return ((n[0] - s[0]) * (e[0] - s[0]) + (n[1] - s[1]) * (e[1] - s[1]))/ (Math.pow(e[0] - s[0], 2) + Math.pow(e[1] - s[1], 2)); } + var action = function(graph) { var way = graph.entity(wayId), nodes = graph.childNodes(way), @@ -30,8 +33,10 @@ export function Straighten(wayId, projection) { p0 = startPoint[0] + u * (endPoint[0] - startPoint[0]), p1 = startPoint[1] + u * (endPoint[1] - startPoint[1]); - graph = graph.replace(graph.entity(node.id) + graph = graph + .replace(graph.entity(node.id) .move(projection.invert([p0, p1]))); + } else { // safe to delete if (toDelete.indexOf(node) === -1) { @@ -41,12 +46,13 @@ export function Straighten(wayId, projection) { } for (i = 0; i < toDelete.length; i++) { - graph = DeleteNode(toDelete[i].id)(graph); + graph = actionDeleteNode(toDelete[i].id)(graph); } return graph; }; + action.disabled = function(graph) { // check way isn't too bendy var way = graph.entity(wayId), @@ -75,5 +81,6 @@ export function Straighten(wayId, projection) { } }; + return action; } diff --git a/modules/actions/unrestrict_turn.js b/modules/actions/unrestrict_turn.js index 6535372b9..3e0a794c5 100644 --- a/modules/actions/unrestrict_turn.js +++ b/modules/actions/unrestrict_turn.js @@ -1,4 +1,5 @@ -import { DeleteRelation } from './delete_relation'; +import { actionDeleteRelation } from './delete_relation'; + // Remove the effects of `turn.restriction` on `turn`, which must have the // following structure: @@ -18,8 +19,8 @@ import { DeleteRelation } from './delete_relation'; // that restriction is also deleted, but at the same time restrictions on // the turns other than the first two are created. // -export function UnrestrictTurn(turn) { +export function actionUnrestrictTurn(turn) { return function(graph) { - return DeleteRelation(turn.restriction)(graph); + return actionDeleteRelation(turn.restriction)(graph); }; } diff --git a/modules/behavior/add_way.js b/modules/behavior/add_way.js index 476a6d779..fe0d34a79 100644 --- a/modules/behavior/add_way.js +++ b/modules/behavior/add_way.js @@ -1,11 +1,12 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; -import { Browse } from '../modes/index'; -import { Draw } from './draw'; +import { utilRebind } from '../util/rebind'; +import { modeBrowse } from '../modes/index'; +import { behaviorDraw } from './draw'; -export function AddWay(context) { + +export function behaviorAddWay(context) { var dispatch = d3.dispatch('start', 'startFromWay', 'startFromNode'), - draw = Draw(context); + draw = behaviorDraw(context); var addWay = function(surface) { draw.on('click', function() { dispatch.apply('start', this, arguments); }) @@ -20,22 +21,26 @@ export function AddWay(context) { surface.call(draw); }; + addWay.off = function(surface) { surface.call(draw.off); }; + addWay.cancel = function() { window.setTimeout(function() { context.map().dblclickEnable(true); }, 1000); - context.enter(Browse(context)); + context.enter(modeBrowse(context)); }; + addWay.tail = function(text) { draw.tail(text); return addWay; }; - return rebind(addWay, dispatch, 'on'); + + return utilRebind(addWay, dispatch, 'on'); } diff --git a/modules/behavior/breathe.js b/modules/behavior/breathe.js index 51fb7f3ec..85a494ea7 100644 --- a/modules/behavior/breathe.js +++ b/modules/behavior/breathe.js @@ -3,7 +3,7 @@ import { transition as d3transition } from 'd3'; import _ from 'lodash'; -export function Breathe() { +export function behaviorBreathe() { var duration = 800, steps = 4, selector = '.selected.shadow, .selected .shadow', diff --git a/modules/behavior/copy.js b/modules/behavior/copy.js index 3f7e4e52c..5f79f1a7e 100644 --- a/modules/behavior/copy.js +++ b/modules/behavior/copy.js @@ -1,17 +1,20 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; -import { cmd } from '../ui/index'; +import { uiCmd } from '../ui/index'; -export function Copy(context) { + +export function behaviorCopy(context) { var keybinding = d3keybinding('copy'); + function groupEntities(ids, graph) { var entities = ids.map(function (id) { return graph.entity(id); }); return _.extend({relation: [], way: [], node: []}, _.groupBy(entities, function(entity) { return entity.type; })); } + function getDescendants(id, graph, descendants) { var entity = graph.entity(id), i, children; @@ -36,6 +39,7 @@ export function Copy(context) { return descendants; } + function doCopy() { d3.event.preventDefault(); if (context.inIntro()) return; @@ -70,15 +74,18 @@ export function Copy(context) { context.copyIDs(canCopy); } + function copy() { - keybinding.on(cmd('⌘C'), doCopy); + keybinding.on(uiCmd('⌘C'), doCopy); d3.select(document).call(keybinding); return copy; } + copy.off = function() { d3.select(document).call(keybinding.off); }; + return copy; } diff --git a/modules/behavior/drag.js b/modules/behavior/drag.js index ec9854e6c..dc79f51f2 100644 --- a/modules/behavior/drag.js +++ b/modules/behavior/drag.js @@ -1,9 +1,13 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; -import { prefixCSSProperty, prefixDOMProperty } from '../util/index'; +import { utilRebind } from '../util/rebind'; +import { + utilPrefixCSSProperty, + utilPrefixDOMProperty +} from '../util/index'; + /* - `iD.behavior.drag` is like `d3.behavior.drag`, with the following differences: + `behaviorDrag` is like `d3.behavior.drag`, with the following differences: * The `origin` function is expected to return an [x, y] tuple rather than an {x, y} object. @@ -18,26 +22,27 @@ import { prefixCSSProperty, prefixDOMProperty } from '../util/index'; * Delegation is supported via the `delegate` function. */ -export function drag() { - function d3_eventCancel() { - d3.event.stopPropagation(); - d3.event.preventDefault(); - } - +export function behaviorDrag() { var event = d3.dispatch('start', 'move', 'end'), origin = null, selector = '', filter = null, event_, target, surface; - function eventOf(thiz, argumentz) { - return function(e1) { - e1.target = drag; - d3.customEvent(e1, event.apply, event, [e1.type, thiz, argumentz]); - }; + + function d3_eventCancel() { + d3.event.stopPropagation(); + d3.event.preventDefault(); } - var d3_event_userSelectProperty = prefixCSSProperty('UserSelect'), + function eventOf(thiz, argumentz) { + return function(e1) { + e1.target = drag; + d3.customEvent(e1, event.apply, event, [e1.type, thiz, argumentz]); + }; + } + + var d3_event_userSelectProperty = utilPrefixCSSProperty('UserSelect'), d3_event_userSelectSuppress = d3_event_userSelectProperty ? function () { var selection = d3.selection(), @@ -54,9 +59,11 @@ export function drag() { }; }; + function mousedown() { target = this; event_ = eventOf(target, arguments); + var eventTarget = d3.event.target, touchId = d3.event.touches ? d3.event.changedTouches[0].identifier : null, offset, @@ -84,8 +91,8 @@ export function drag() { })[0] : d3.mouse(p); } - function dragmove() { + function dragmove() { var p = point(), dx = p[0] - origin_[0], dy = p[1] - origin_[1]; @@ -110,6 +117,7 @@ export function drag() { }); } + function dragend() { if (started) { event_({ @@ -125,14 +133,16 @@ export function drag() { selectEnable(); } + function click() { d3_eventCancel(); w.on('click.drag', null); } } + function drag(selection) { - var matchesSelector = prefixDOMProperty('matchesSelector'), + var matchesSelector = utilPrefixDOMProperty('matchesSelector'), delegate = mousedown; if (selector) { @@ -152,29 +162,34 @@ export function drag() { .on('touchstart.drag' + selector, delegate); } + drag.off = function(selection) { selection.on('mousedown.drag' + selector, null) .on('touchstart.drag' + selector, null); }; + drag.delegate = function(_) { if (!arguments.length) return selector; selector = _; return drag; }; + drag.filter = function(_) { if (!arguments.length) return origin; filter = _; return drag; }; + drag.origin = function (_) { if (!arguments.length) return origin; origin = _; return drag; }; + drag.cancel = function() { d3.select(window) .on('mousemove.drag', null) @@ -182,6 +197,7 @@ export function drag() { return drag; }; + drag.target = function() { if (!arguments.length) return target; target = arguments[0]; @@ -189,11 +205,13 @@ export function drag() { return drag; }; + drag.surface = function() { if (!arguments.length) return surface; surface = arguments[0]; return drag; }; - return rebind(drag, event, 'on'); + + return utilRebind(drag, event, 'on'); } diff --git a/modules/behavior/draw.js b/modules/behavior/draw.js index 1217d850f..da54427b2 100644 --- a/modules/behavior/draw.js +++ b/modules/behavior/draw.js @@ -1,25 +1,32 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; import { d3keybinding } from '../lib/d3.keybinding.js'; -import { chooseEdge, euclideanDistance } from '../geo/index'; -import { Edit } from './edit'; -import { Hover } from './hover'; -import { Tail } from './tail'; +import { behaviorEdit } from './edit'; +import { behaviorHover } from './hover'; +import { behaviorTail } from './tail'; +import { geoChooseEdge, geoEuclideanDistance } from '../geo/index'; +import { utilRebind } from '../util/rebind'; -export function Draw(context) { + +behaviorDraw.usedTails = {}; +behaviorDraw.disableSpace = false; +behaviorDraw.lastSpace = null; + + +export function behaviorDraw(context) { var dispatch = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'undo', 'cancel', 'finish'), keybinding = d3keybinding('draw'), - hover = Hover(context) + hover = behaviorHover(context) .altDisables(true) .on('hover', context.ui().sidebar.hover), - tail = Tail(), - edit = Edit(context), + tail = behaviorTail(), + edit = behaviorEdit(context), closeTolerance = 4, tolerance = 12, mouseLeave = false, lastMouse = null, - cached = Draw; + cached = behaviorDraw; + function datum() { if (d3.event.altKey) return {}; @@ -31,6 +38,7 @@ export function Draw(context) { } } + function mousedown() { function point() { @@ -50,7 +58,7 @@ export function Draw(context) { d3.select(window).on('mouseup.draw', function() { var t2 = +new Date(), p2 = point(), - dist = euclideanDistance(p1, p2); + dist = geoEuclideanDistance(p1, p2); element.on('mousemove.draw', mousemove); d3.select(window).on('mouseup.draw', null); @@ -73,19 +81,23 @@ export function Draw(context) { }, true); } + function mousemove() { lastMouse = d3.event; dispatch.call('move', this, datum()); } + function mouseenter() { mouseLeave = false; } + function mouseleave() { mouseLeave = true; } + function click() { var d = datum(); if (d.type === 'way') { @@ -96,7 +108,7 @@ export function Draw(context) { mouse[1] > pad && mouse[1] < dims[1] - pad; if (trySnap) { - var choice = chooseEdge(context.childNodes(d), context.mouse(), context.projection), + var choice = geoChooseEdge(context.childNodes(d), context.mouse(), context.projection), edge = [d.nodes[choice.index - 1], d.nodes[choice.index]]; dispatch.call('clickWay', this, choice.loc, edge); } else { @@ -111,10 +123,11 @@ export function Draw(context) { } } + function space() { var currSpace = context.mouse(); if (cached.disableSpace && cached.lastSpace) { - var dist = euclideanDistance(cached.lastSpace, currSpace); + var dist = geoEuclideanDistance(cached.lastSpace, currSpace); if (dist > tolerance) { cached.disableSpace = false; } @@ -135,21 +148,25 @@ export function Draw(context) { click(); } + function backspace() { d3.event.preventDefault(); dispatch.call('undo'); } + function del() { d3.event.preventDefault(); dispatch.call('cancel'); } + function ret() { d3.event.preventDefault(); dispatch.call('finish'); } + function draw(selection) { context.install(hover); context.install(edit); @@ -178,6 +195,7 @@ export function Draw(context) { return draw; } + draw.off = function(selection) { context.ui().sidebar.hover.cancel(); context.uninstall(hover); @@ -202,14 +220,12 @@ export function Draw(context) { .call(keybinding.off); }; + draw.tail = function(_) { tail.text(_); return draw; }; - return rebind(draw, dispatch, 'on'); -} -Draw.usedTails = {}; -Draw.disableSpace = false; -Draw.lastSpace = null; + return utilRebind(draw, dispatch, 'on'); +} diff --git a/modules/behavior/draw_way.js b/modules/behavior/draw_way.js index f07d6f388..011613d5f 100644 --- a/modules/behavior/draw_way.js +++ b/modules/behavior/draw_way.js @@ -1,39 +1,69 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { AddEntity, AddMidpoint, AddVertex, MoveNode } from '../actions/index'; -import { Browse, Select } from '../modes/index'; -import { Node, Way } from '../core/index'; -import { chooseEdge, edgeEqual } from '../geo/index'; -import { Draw } from './draw'; -import { entitySelector, functor } from '../util/index'; -export function DrawWay(context, wayId, index, mode, baseGraph) { +import { + actionAddEntity, + actionAddMidpoint, + actionAddVertex, + actionMoveNode +} from '../actions/index'; + +import { + modeBrowse, + modeSelect +} from '../modes/index'; + +import { + coreNode, + coreWay +} from '../core/index'; + +import { + geoChooseEdge, + geoEdgeEqual +} from '../geo/index'; + +import { + behaviorDraw +} from './draw'; + +import { + utilEntitySelector, + utilFunctor +} from '../util/index'; + + +export function behaviorDrawWay(context, wayId, index, mode, baseGraph) { + var way = context.entity(wayId), isArea = context.geometry(wayId) === 'area', finished = false, annotation = t((way.isDegenerate() ? 'operations.start.annotation.' : 'operations.continue.annotation.') + context.geometry(wayId)), - draw = Draw(context); + draw = behaviorDraw(context); var startIndex = typeof index === 'undefined' ? way.nodes.length - 1 : 0, - start = Node({loc: context.graph().entity(way.nodes[startIndex]).loc}), - end = Node({loc: context.map().mouseCoordinates()}), - segment = Way({ + start = coreNode({loc: context.graph().entity(way.nodes[startIndex]).loc}), + end = coreNode({loc: context.map().mouseCoordinates()}), + segment = coreWay({ nodes: typeof index === 'undefined' ? [start.id, end.id] : [end.id, start.id], tags: _.clone(way.tags) }); - var f = context[way.isDegenerate() ? 'replace' : 'perform']; + var fn = context[way.isDegenerate() ? 'replace' : 'perform']; if (isArea) { - f(AddEntity(end), - AddVertex(wayId, end.id, index)); + fn(actionAddEntity(end), + actionAddVertex(wayId, end.id, index) + ); } else { - f(AddEntity(start), - AddEntity(end), - AddEntity(segment)); + fn(actionAddEntity(start), + actionAddEntity(end), + actionAddEntity(segment) + ); } + function move(datum) { var loc; @@ -48,7 +78,7 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { mouse[1] > pad && mouse[1] < dims[1] - pad; if (trySnap) { - loc = chooseEdge(context.childNodes(datum), context.mouse(), context.projection).loc; + loc = geoChooseEdge(context.childNodes(datum), context.mouse(), context.projection).loc; } } @@ -56,20 +86,23 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { loc = context.map().mouseCoordinates(); } - context.replace(MoveNode(end.id, loc)); + context.replace(actionMoveNode(end.id, loc)); } + function undone() { finished = true; - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } + function setActiveElements() { var active = isArea ? [wayId, end.id] : [segment.id, start.id, end.id]; - context.surface().selectAll(entitySelector(active)) + context.surface().selectAll(utilEntitySelector(active)) .classed('active', true); } + var drawWay = function(surface) { draw.on('move', move) .on('click', drawWay.add) @@ -91,6 +124,7 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { .on('undone.draw', undone); }; + drawWay.off = function(surface) { if (!finished) context.pop(); @@ -106,6 +140,7 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { .on('undone.draw', null); }; + function ReplaceTemporaryNode(newNode) { return function(graph) { if (isArea) { @@ -123,24 +158,26 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { }; } + // Accept the current position of the temporary node and continue drawing. drawWay.add = function(loc) { - // prevent duplicate nodes var last = context.hasEntity(way.nodes[way.nodes.length - (isArea ? 2 : 1)]); if (last && last.loc[0] === loc[0] && last.loc[1] === loc[1]) return; - var newNode = Node({loc: loc}); + var newNode = coreNode({loc: loc}); context.replace( - AddEntity(newNode), + actionAddEntity(newNode), ReplaceTemporaryNode(newNode), - annotation); + annotation + ); finished = true; context.enter(mode); }; + // Connect the way to an existing way. drawWay.addWay = function(loc, edge) { var previousEdge = startIndex ? @@ -148,34 +185,37 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { [way.nodes[0], way.nodes[1]]; // Avoid creating duplicate segments - if (!isArea && edgeEqual(edge, previousEdge)) + if (!isArea && geoEdgeEqual(edge, previousEdge)) return; - var newNode = Node({ loc: loc }); + var newNode = coreNode({ loc: loc }); context.perform( - AddMidpoint({ loc: loc, edge: edge}, newNode), + actionAddMidpoint({ loc: loc, edge: edge}, newNode), ReplaceTemporaryNode(newNode), - annotation); + annotation + ); finished = true; context.enter(mode); }; + // Connect the way to an existing node and continue drawing. drawWay.addNode = function(node) { - // Avoid creating duplicate segments if (way.areAdjacent(node.id, way.nodes[way.nodes.length - 1])) return; context.perform( ReplaceTemporaryNode(node), - annotation); + annotation + ); finished = true; context.enter(mode); }; + // Finish the draw operation, removing the temporary node. If the way has enough // nodes to be valid, it's selected. Otherwise, return to browse mode. drawWay.finish = function() { @@ -188,18 +228,18 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { if (context.hasEntity(wayId)) { context.enter( - Select(context, [wayId]) - .suppressMenu(true) - .newFeature(true)); + modeSelect(context, [wayId]).suppressMenu(true).newFeature(true) + ); } else { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } }; + // Cancel the draw operation and return to browse, deleting everything drawn. drawWay.cancel = function() { context.perform( - functor(baseGraph), + utilFunctor(baseGraph), t('operations.cancel_draw.annotation')); window.setTimeout(function() { @@ -207,13 +247,15 @@ export function DrawWay(context, wayId, index, mode, baseGraph) { }, 1000); finished = true; - context.enter(Browse(context)); + context.enter(modeBrowse(context)); }; + drawWay.tail = function(text) { draw.tail(text); return drawWay; }; + return drawWay; } diff --git a/modules/behavior/edit.js b/modules/behavior/edit.js index 33e61b1aa..d4ed0f6b9 100644 --- a/modules/behavior/edit.js +++ b/modules/behavior/edit.js @@ -1,13 +1,16 @@ -export function Edit(context) { +export function behaviorEdit(context) { + function edit() { context.map() .minzoom(context.minEditableZoom()); } + edit.off = function() { context.map() .minzoom(0); }; + return edit; } diff --git a/modules/behavior/hash.js b/modules/behavior/hash.js index c696a6787..43d4b5270 100644 --- a/modules/behavior/hash.js +++ b/modules/behavior/hash.js @@ -1,13 +1,15 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { qsString, stringQs } from '../util/index'; +import { utilQsString, utilStringQs } from '../util/index'; -export function Hash(context) { + +export function behaviorHash(context) { var s0 = null, // cached location.hash lat = 90 - 1e-8; // allowable latitude range + var parser = function(map, s) { - var q = stringQs(s); + var q = utilStringQs(s); var args = (q.map || '').split('/').map(Number); if (args.length < 3 || args.some(isNaN)) { return true; // replace bogus hash @@ -17,12 +19,13 @@ export function Hash(context) { } }; + var formatter = function(map) { var mode = context.mode(), center = map.center(), zoom = map.zoom(), precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)), - q = _.omit(stringQs(location.hash.substring(1)), 'comment'), + q = _.omit(utilStringQs(location.hash.substring(1)), 'comment'), newParams = {}; if (mode && mode.id === 'browse') { @@ -40,17 +43,20 @@ export function Hash(context) { '/' + center[0].toFixed(precision) + '/' + center[1].toFixed(precision); - return '#' + qsString(_.assign(q, newParams), true); + return '#' + utilQsString(_.assign(q, newParams), true); }; + function update() { if (context.inIntro()) return; var s1 = formatter(context.map()); if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map! } + var throttledUpdate = _.throttle(update, 500); + function hashchange() { if (location.hash === s0) return; // ignore spurious hashchange events if (parser(context.map(), (s0 = location.hash).substring(1))) { @@ -58,6 +64,7 @@ export function Hash(context) { } } + function hash() { context.map() .on('move.hash', throttledUpdate); @@ -69,7 +76,7 @@ export function Hash(context) { .on('hashchange.hash', hashchange); if (location.hash) { - var q = stringQs(location.hash.substring(1)); + var q = utilStringQs(location.hash.substring(1)); if (q.id) context.zoomToEntity(q.id.split(',')[0], !q.map); if (q.comment) context.storage('comment', q.comment); hashchange(); @@ -77,6 +84,7 @@ export function Hash(context) { } } + hash.off = function() { throttledUpdate.cancel(); @@ -92,5 +100,6 @@ export function Hash(context) { location.hash = ''; }; + return hash; } diff --git a/modules/behavior/hover.js b/modules/behavior/hover.js index a28ebffd8..5f4ec963d 100644 --- a/modules/behavior/hover.js +++ b/modules/behavior/hover.js @@ -1,7 +1,8 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; import { d3keybinding } from '../lib/d3.keybinding.js'; -import { Entity } from '../core/index'; +import { coreEntity } from '../core/index'; +import { utilRebind } from '../util/rebind'; + /* The hover behavior adds the `.hover` class on mouseover to all elements to which @@ -12,13 +13,14 @@ import { Entity } from '../core/index'; Only one of these elements can have the :hover pseudo-class, but all of them will have the .hover class. */ -export function Hover() { +export function behaviorHover() { var dispatch = d3.dispatch('hover'), selection = d3.select(null), buttonDown, altDisables, target; + function keydown() { if (altDisables && d3.event.keyCode === d3keybinding.modifierCodes.alt) { dispatch.call('hover', this, null); @@ -28,6 +30,7 @@ export function Hover() { } } + function keyup() { if (altDisables && d3.event.keyCode === d3keybinding.modifierCodes.alt) { dispatch.call('hover', this, target ? target.id : null); @@ -37,6 +40,7 @@ export function Hover() { } } + var hover = function(__) { selection = __; @@ -50,7 +54,7 @@ export function Hover() { selection.selectAll('.hover-suppressed') .classed('hover-suppressed', false); - if (target instanceof Entity) { + if (target instanceof coreEntity) { var selector = '.' + target.id; if (target.type === 'relation') { @@ -133,5 +137,5 @@ export function Hover() { }; - return rebind(hover, dispatch, 'on'); + return utilRebind(hover, dispatch, 'on'); } diff --git a/modules/behavior/index.js b/modules/behavior/index.js index 6a04e35bc..da78fa23a 100644 --- a/modules/behavior/index.js +++ b/modules/behavior/index.js @@ -1,13 +1,13 @@ -export { AddWay } from './add_way'; -export { Breathe } from './breathe'; -export { Copy } from './copy'; -export { drag } from './drag'; -export { DrawWay } from './draw_way'; -export { Draw } from './draw'; -export { Edit } from './edit'; -export { Hash } from './hash'; -export { Hover } from './hover'; -export { Lasso } from './lasso'; -export { Paste } from './paste'; -export { Select } from './select'; -export { Tail } from './tail'; +export { behaviorAddWay } from './add_way'; +export { behaviorBreathe } from './breathe'; +export { behaviorCopy } from './copy'; +export { behaviorDrag } from './drag'; +export { behaviorDrawWay } from './draw_way'; +export { behaviorDraw } from './draw'; +export { behaviorEdit } from './edit'; +export { behaviorHash } from './hash'; +export { behaviorHover } from './hover'; +export { behaviorLasso } from './lasso'; +export { behaviorPaste } from './paste'; +export { behaviorSelect } from './select'; +export { behaviorTail } from './tail'; diff --git a/modules/behavior/lasso.js b/modules/behavior/lasso.js index 9c9ce10f9..f42be7ccd 100644 --- a/modules/behavior/lasso.js +++ b/modules/behavior/lasso.js @@ -1,14 +1,16 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { Extent, pointInPolygon } from '../geo/index'; -import { Select } from '../modes/index'; -import { Lasso as uiLasso } from '../ui/index'; +import { geoExtent, geoPointInPolygon } from '../geo/index'; +import { modeSelect } from '../modes/index'; +import { uiLasso } from '../ui/index'; -export function Lasso(context) { + +export function behaviorLasso(context) { var behavior = function(selection) { var lasso; + function mousedown() { var button = 0; // left if (d3.event.button === button && d3.event.shiftKey === true) { @@ -22,6 +24,7 @@ export function Lasso(context) { } } + function mousemove() { if (!lasso) { lasso = uiLasso(context); @@ -31,26 +34,29 @@ export function Lasso(context) { lasso.p(context.mouse()); } + function normalize(a, b) { return [ [Math.min(a[0], b[0]), Math.min(a[1], b[1])], [Math.max(a[0], b[0]), Math.max(a[1], b[1])]]; } + function lassoed() { if (!lasso) return []; var graph = context.graph(), bounds = lasso.extent().map(context.projection.invert), - extent = Extent(normalize(bounds[0], bounds[1])); + extent = geoExtent(normalize(bounds[0], bounds[1])); return _.map(context.intersects(extent).filter(function(entity) { return entity.type === 'node' && - pointInPolygon(context.projection(entity.loc), lasso.coordinates) && + geoPointInPolygon(context.projection(entity.loc), lasso.coordinates) && !context.features().isHidden(entity, graph, entity.geometry(graph)); }), 'id'); } + function mouseup() { selection .on('mousemove.lasso', null) @@ -62,7 +68,7 @@ export function Lasso(context) { lasso.close(); if (ids.length) { - context.enter(Select(context, ids)); + context.enter(modeSelect(context, ids)); } } @@ -70,9 +76,11 @@ export function Lasso(context) { .on('mousedown.lasso', mousedown); }; + behavior.off = function(selection) { selection.on('mousedown.lasso', null); }; + return behavior; } diff --git a/modules/behavior/paste.js b/modules/behavior/paste.js index 2c426efee..a8898cf22 100644 --- a/modules/behavior/paste.js +++ b/modules/behavior/paste.js @@ -1,12 +1,23 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; -import { ChangeTags, CopyEntities, Move as MoveAction} from '../actions/index'; -import { Extent, pointInPolygon } from '../geo/index'; -import { Move as MoveMode } from '../modes/index'; -import { cmd } from '../ui/index'; -export function Paste(context) { +import { + actionChangeTags, + actionCopyEntities, + actionMove +} from '../actions/index'; + +import { + geoExtent, + geoPointInPolygon +} from '../geo/index'; + +import { modeMove } from '../modes/index'; +import { uiCmd } from '../ui/index'; + + +export function behaviorPaste(context) { var keybinding = d3keybinding('paste'); function omitTag(v, k) { @@ -25,6 +36,7 @@ export function Paste(context) { ); } + function doPaste() { d3.event.preventDefault(); if (context.inIntro()) return; @@ -32,18 +44,18 @@ export function Paste(context) { var baseGraph = context.graph(), mouse = context.mouse(), projection = context.projection, - viewport = Extent(projection.clipExtent()).polygon(); + viewport = geoExtent(projection.clipExtent()).polygon(); - if (!pointInPolygon(mouse, viewport)) return; + if (!geoPointInPolygon(mouse, viewport)) return; - var extent = Extent(), + var extent = geoExtent(), oldIDs = context.copyIDs(), oldGraph = context.copyGraph(), newIDs = []; if (!oldIDs.length) return; - var action = CopyEntities(oldIDs, oldGraph); + var action = actionCopyEntities(oldIDs, oldGraph); context.perform(action); var copies = action.copies(); @@ -53,26 +65,31 @@ export function Paste(context) { extent._extend(oldEntity.extent(oldGraph)); newIDs.push(newEntity.id); - context.perform(ChangeTags(newEntity.id, _.omit(newEntity.tags, omitTag))); + context.perform( + actionChangeTags(newEntity.id, _.omit(newEntity.tags, omitTag)) + ); } // Put pasted objects where mouse pointer is.. var center = projection(extent.center()), delta = [ mouse[0] - center[0], mouse[1] - center[1] ]; - context.perform(MoveAction(newIDs, delta, projection)); - context.enter(MoveMode(context, newIDs, baseGraph)); + context.perform(actionMove(newIDs, delta, projection)); + context.enter(modeMove(context, newIDs, baseGraph)); } + function paste() { - keybinding.on(cmd('⌘V'), doPaste); + keybinding.on(uiCmd('⌘V'), doPaste); d3.select(document).call(keybinding); return paste; } + paste.off = function() { d3.select(document).call(keybinding.off); }; + return paste; } diff --git a/modules/behavior/select.js b/modules/behavior/select.js index b7f23812e..bf3b7f104 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -1,9 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { Browse, Select as SelectMode } from '../modes/index'; -import { Entity } from '../core/index'; +import { modeBrowse, modeSelect } from '../modes/index'; +import { coreEntity } from '../core/index'; + + +export function behaviorSelect(context) { -export function Select(context) { function keydown() { if (d3.event && d3.event.shiftKey) { context.surface() @@ -11,6 +13,7 @@ export function Select(context) { } } + function keyup() { if (!d3.event || !d3.event.shiftKey) { context.surface() @@ -18,33 +21,33 @@ export function Select(context) { } } + function click() { var datum = d3.event.target.__data__, lasso = d3.select('#surface .lasso').node(), mode = context.mode(); - if (!(datum instanceof Entity)) { + if (!(datum instanceof coreEntity)) { if (!d3.event.shiftKey && !lasso && mode.id !== 'browse') - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } else if (!d3.event.shiftKey && !lasso) { // Avoid re-entering Select mode with same entity. if (context.selectedIDs().length !== 1 || context.selectedIDs()[0] !== datum.id) { - context.enter(SelectMode(context, [datum.id])); + context.enter(modeSelect(context, [datum.id])); } else { mode.suppressMenu(false).reselect(); } } else if (context.selectedIDs().indexOf(datum.id) >= 0) { var selectedIDs = _.without(context.selectedIDs(), datum.id); - context.enter(selectedIDs.length ? - SelectMode(context, selectedIDs) : - Browse(context)); + context.enter(selectedIDs.length ? modeSelect(context, selectedIDs) : modeBrowse(context)); } else { - context.enter(SelectMode(context, context.selectedIDs().concat([datum.id]))); + context.enter(modeSelect(context, context.selectedIDs().concat([datum.id]))); } } + var behavior = function(selection) { d3.select(window) .on('keydown.select', keydown) @@ -55,6 +58,7 @@ export function Select(context) { keydown(); }; + behavior.off = function(selection) { d3.select(window) .on('keydown.select', null) @@ -65,5 +69,6 @@ export function Select(context) { keyup(); }; + return behavior; } diff --git a/modules/behavior/tail.js b/modules/behavior/tail.js index 630388f4f..9b6b86c37 100644 --- a/modules/behavior/tail.js +++ b/modules/behavior/tail.js @@ -1,44 +1,21 @@ import * as d3 from 'd3'; -import { setTransform } from '../util/index'; -import { getDimensions } from '../util/dimensions'; +import { utilSetTransform } from '../util/index'; +import { utilGetDimensions } from '../util/dimensions'; -export function Tail() { + +export function behaviorTail() { var text, container, xmargin = 25, tooltipSize = [0, 0], selectionSize = [0, 0]; + function tail(selection) { if (!text) return; d3.select(window) - .on('resize.tail', function() { selectionSize = getDimensions(selection); }); - - function show() { - container.style('display', 'block'); - tooltipSize = getDimensions(container); - } - - function mousemove() { - if (container.style('display') === 'none') show(); - var xoffset = ((d3.event.clientX + tooltipSize[0] + xmargin) > selectionSize[0]) ? - -tooltipSize[0] - xmargin : xmargin; - container.classed('left', xoffset > 0); - setTransform(container, d3.event.clientX + xoffset, d3.event.clientY); - } - - function mouseleave() { - if (d3.event.relatedTarget !== container.node()) { - container.style('display', 'none'); - } - } - - function mouseenter() { - if (d3.event.relatedTarget !== container.node()) { - show(); - } - } + .on('resize.tail', function() { selectionSize = utilGetDimensions(selection); }); container = d3.select(document.body) .append('div') @@ -56,10 +33,40 @@ export function Tail() { container .on('mousemove.tail', mousemove); - tooltipSize = getDimensions(container); - selectionSize = getDimensions(selection); + tooltipSize = utilGetDimensions(container); + selectionSize = utilGetDimensions(selection); + + + function show() { + container.style('display', 'block'); + tooltipSize = utilGetDimensions(container); + } + + + function mousemove() { + if (container.style('display') === 'none') show(); + var xoffset = ((d3.event.clientX + tooltipSize[0] + xmargin) > selectionSize[0]) ? + -tooltipSize[0] - xmargin : xmargin; + container.classed('left', xoffset > 0); + utilSetTransform(container, d3.event.clientX + xoffset, d3.event.clientY); + } + + + function mouseleave() { + if (d3.event.relatedTarget !== container.node()) { + container.style('display', 'none'); + } + } + + + function mouseenter() { + if (d3.event.relatedTarget !== container.node()) { + show(); + } + } } + tail.off = function(selection) { if (!text) return; @@ -76,11 +83,13 @@ export function Tail() { .on('resize.tail', null); }; + tail.text = function(_) { if (!arguments.length) return text; text = _; return tail; }; + return tail; } diff --git a/modules/core/connection.js b/modules/core/connection.js index 3b12d74a6..06139eca2 100644 --- a/modules/core/connection.js +++ b/modules/core/connection.js @@ -1,18 +1,19 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; -import { functor } from '../util/index'; +import { utilRebind } from '../util/rebind'; +import { utilFunctor } from '../util/index'; import { d3geoTile } from '../lib/d3.geo.tile'; -import { Detect } from '../util/detect'; -import { Entity } from './entity'; -import { Extent } from '../geo/index'; +import { geoExtent } from '../geo/index'; +import { utilDetect } from '../util/detect'; +import { coreEntity } from './entity'; +import { coreNode } from './node'; +import { coreRelation } from './relation'; +import { coreWay } from './way'; import { JXON } from '../util/jxon'; -import { Node } from './node'; -import { Relation } from './relation'; -import { Way } from './way'; import osmAuth from 'osm-auth'; -export function Connection(useHttps) { + +export function coreConnection(useHttps) { if (typeof useHttps !== 'boolean') { useHttps = window.location.protocol === 'https:'; } @@ -45,6 +46,7 @@ export function Connection(useHttps) { return url + '/changeset/' + changesetId; }; + connection.changesetsURL = function(center, zoom) { var precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); return url + '/history#map=' + @@ -53,14 +55,17 @@ export function Connection(useHttps) { center[0].toFixed(precision); }; + connection.entityURL = function(entity) { return url + '/' + entity.type + '/' + entity.osmId(); }; + connection.userURL = function(username) { return url + '/user/' + username; }; + connection.loadFromURL = function(url, callback) { function done(err, dom) { return callback(err, parse(dom)); @@ -68,9 +73,10 @@ export function Connection(useHttps) { return d3.xml(url).get(done); }; + connection.loadEntity = function(id, callback) { - var type = Entity.id.type(id), - osmID = Entity.id.toOSM(id); + var type = coreEntity.id.type(id), + osmID = coreEntity.id.toOSM(id); connection.loadFromURL( url + '/api/0.6/' + type + '/' + osmID + (type !== 'node' ? '/full' : ''), @@ -79,9 +85,10 @@ export function Connection(useHttps) { }); }; + connection.loadEntityVersion = function(id, version, callback) { - var type = Entity.id.type(id), - osmID = Entity.id.toOSM(id); + var type = coreEntity.id.type(id), + osmID = coreEntity.id.toOSM(id); connection.loadFromURL( url + '/api/0.6/' + type + '/' + osmID + '/' + version, @@ -90,10 +97,11 @@ export function Connection(useHttps) { }); }; + connection.loadMultiple = function(ids, callback) { - _.each(_.groupBy(_.uniq(ids), Entity.id.type), function(v, k) { + _.each(_.groupBy(_.uniq(ids), coreEntity.id.type), function(v, k) { var type = k + 's', - osmIDs = _.map(v, Entity.id.toOSM); + osmIDs = _.map(v, coreEntity.id.toOSM); _.each(_.chunk(osmIDs, 150), function(arr) { connection.loadFromURL( @@ -105,20 +113,24 @@ export function Connection(useHttps) { }); }; + function authenticating() { dispatch.call('authenticating'); } + function authenticated() { dispatch.call('authenticated'); } + function getLoc(attrs) { var lon = attrs.lon && attrs.lon.value, lat = attrs.lat && attrs.lat.value; return [parseFloat(lon), parseFloat(lat)]; } + function getNodes(obj) { var elems = obj.getElementsByTagName(ndStr), nodes = new Array(elems.length); @@ -128,6 +140,7 @@ export function Connection(useHttps) { return nodes; } + function getTags(obj) { var elems = obj.getElementsByTagName(tagStr), tags = {}; @@ -138,6 +151,7 @@ export function Connection(useHttps) { return tags; } + function getMembers(obj) { var elems = obj.getElementsByTagName(memberStr), members = new Array(elems.length); @@ -152,15 +166,17 @@ export function Connection(useHttps) { return members; } + function getVisible(attrs) { return (!attrs.visible || attrs.visible.value !== 'false'); } + var parsers = { node: function nodeData(obj) { var attrs = obj.attributes; - return new Node({ - id: Entity.id.fromOSM(nodeStr, attrs.id.value), + return new coreNode({ + id: coreEntity.id.fromOSM(nodeStr, attrs.id.value), loc: getLoc(attrs), version: attrs.version.value, user: attrs.user && attrs.user.value, @@ -171,8 +187,8 @@ export function Connection(useHttps) { way: function wayData(obj) { var attrs = obj.attributes; - return new Way({ - id: Entity.id.fromOSM(wayStr, attrs.id.value), + return new coreWay({ + id: coreEntity.id.fromOSM(wayStr, attrs.id.value), version: attrs.version.value, user: attrs.user && attrs.user.value, tags: getTags(obj), @@ -183,8 +199,8 @@ export function Connection(useHttps) { relation: function relationData(obj) { var attrs = obj.attributes; - return new Relation({ - id: Entity.id.fromOSM(relationStr, attrs.id.value), + return new coreRelation({ + id: coreEntity.id.fromOSM(relationStr, attrs.id.value), version: attrs.version.value, user: attrs.user && attrs.user.value, tags: getTags(obj), @@ -194,6 +210,7 @@ export function Connection(useHttps) { } }; + function parse(dom) { if (!dom || !dom.childNodes) return; @@ -212,10 +229,12 @@ export function Connection(useHttps) { return entities; } + connection.authenticated = function() { return oauth.authenticated(); }; + // Generate Changeset XML. Returns a string. connection.changesetJXON = function(tags) { return { @@ -231,6 +250,7 @@ export function Connection(useHttps) { }; }; + // Generate [osmChange](http://wiki.openstreetmap.org/wiki/OsmChange) // XML. Returns a string. connection.osmChangeJXON = function(changeset_id, changes) { @@ -263,8 +283,9 @@ export function Connection(useHttps) { }; }; + connection.changesetTags = function(version, comment, imageryUsed) { - var detected = Detect(), + var detected = utilDetect(), tags = { created_by: ('iD ' + version).substr(0, 255), imagery_used: imageryUsed.join(';').substr(0, 255), @@ -279,6 +300,7 @@ export function Connection(useHttps) { return tags; }; + connection.putChangeset = function(changes, version, comment, imageryUsed, callback) { oauth.xhr({ method: 'PUT', @@ -302,11 +324,12 @@ export function Connection(useHttps) { method: 'PUT', path: '/api/0.6/changeset/' + changeset_id + '/close', options: { header: { 'Content-Type': 'text/xml' } } - }, functor(true)); + }, utilFunctor(true)); }); }); }; + connection.userDetails = function(callback) { if (userDetails) { callback(undefined, userDetails); @@ -336,6 +359,7 @@ export function Connection(useHttps) { oauth.xhr({ method: 'GET', path: '/api/0.6/user/details' }, done); }; + connection.userChangesets = function(callback) { connection.userDetails(function(err, user) { if (err) return callback(err); @@ -353,6 +377,7 @@ export function Connection(useHttps) { }); }; + connection.status = function(callback) { function done(capabilities) { var apiStatus = capabilities.getElementsByTagName('status'); @@ -363,7 +388,11 @@ export function Connection(useHttps) { .on('error', callback); }; - function abortRequest(i) { i.abort(); } + + function abortRequest(i) { + i.abort(); + } + connection.tileZoom = function(_) { if (!arguments.length) return tileZoom; @@ -371,8 +400,8 @@ export function Connection(useHttps) { return connection; }; - connection.loadTiles = function(projection, dimensions, callback) { + connection.loadTiles = function(projection, dimensions, callback) { if (off) return; var s = projection.scale() * 2 * Math.PI, @@ -433,6 +462,7 @@ export function Connection(useHttps) { }); }; + connection.switch = function(options) { url = options.url; oauth.options(_.extend({ @@ -444,11 +474,13 @@ export function Connection(useHttps) { return connection; }; + connection.toggle = function(_) { off = !_; return connection; }; + connection.flush = function() { userDetails = undefined; _.forEach(inflight, abortRequest); @@ -457,12 +489,14 @@ export function Connection(useHttps) { return connection; }; + connection.loadedTiles = function(_) { if (!arguments.length) return loadedTiles; loadedTiles = _; return connection; }; + connection.logout = function() { userDetails = undefined; oauth.logout(); @@ -470,6 +504,7 @@ export function Connection(useHttps) { return connection; }; + connection.authenticate = function(callback) { userDetails = undefined; function done(err, res) { @@ -479,5 +514,6 @@ export function Connection(useHttps) { return oauth.authenticate(done); }; - return rebind(connection, dispatch, 'on'); + + return utilRebind(connection, dispatch, 'on'); } diff --git a/modules/core/context.js b/modules/core/context.js index 139819db2..3e550ae41 100644 --- a/modules/core/context.js +++ b/modules/core/context.js @@ -1,19 +1,20 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; import { t, addTranslation, setLocale } from '../util/locale'; -import { Background } from '../renderer/background'; -import { Connection } from './connection'; -import { Detect } from '../util/detect'; -import { Features } from '../renderer/features'; -import { History } from './history'; -import { Map } from '../renderer/map'; -import { Select } from '../modes/select'; -import { RawMercator } from '../geo/raw_mercator'; -import { presets as presetsInit } from '../presets/presets'; -import { init as uiInit } from '../ui/init'; -import { locales, en } from '../../data/index'; +import { coreConnection } from './connection'; +import { coreHistory } from './history'; +import { dataLocales, dataEn } from '../../data/index'; +import { geoRawMercator } from '../geo/raw_mercator'; +import { modeSelect } from '../modes/select'; +import { presetInit } from '../presets/init'; +import { rendererBackground } from '../renderer/background'; +import { rendererFeatures } from '../renderer/features'; +import { rendererMap } from '../renderer/map'; import * as services from '../services/index'; +import { uiInit } from '../ui/init'; +import { utilDetect } from '../util/detect'; +import { utilRebind } from '../util/rebind'; + export var areaKeys = {}; @@ -21,13 +22,14 @@ export function setAreaKeys(value) { areaKeys = value; } -export function Context(root) { + +export function coreContext(root) { if (!root.locale) { root.locale = { current: function(_) { this._current = _; } }; } - addTranslation('en', en); + addTranslation('en', dataEn); setLocale('en'); var dispatch = d3.dispatch('enter', 'exit', 'change'), @@ -106,7 +108,7 @@ export function Context(root) { if (!context.hasEntity(id)) return; map.on('drawn.zoomToEntity', null); context.on('enter.zoomToEntity', null); - context.enter(Select(context, [id])); + context.enter(modeSelect(context, [id])); }); context.on('enter.zoomToEntity', function() { @@ -340,7 +342,7 @@ export function Context(root) { }; context.loadLocale = function(cb) { - if (locale && locale !== 'en' && locales.indexOf(locale) !== -1) { + if (locale && locale !== 'en' && dataLocales.indexOf(locale) !== -1) { localePath = localePath || context.asset('locales/' + locale + '.json'); d3.json(localePath, function(err, result) { addTranslation(locale, result); @@ -356,14 +358,14 @@ export function Context(root) { /* Init */ context.version = '2.0.0-alpha.1'; - context.projection = RawMercator(); + context.projection = geoRawMercator(); - locale = Detect().locale; - if (locale && locales.indexOf(locale) === -1) { + locale = utilDetect().locale; + if (locale && dataLocales.indexOf(locale) === -1) { locale = locale.split('-')[0]; } - history = History(context); + history = coreHistory(context); context.graph = history.graph; context.changes = history.changes; context.intersects = history.intersects; @@ -388,13 +390,12 @@ export function Context(root) { ui = uiInit(context); - connection = Connection(); + connection = coreConnection(); - background = Background(context); + background = rendererBackground(context); + features = rendererFeatures(context); - features = Features(context); - - map = Map(context); + map = rendererMap(context); context.mouse = map.mouse; context.extent = map.extent; context.pan = map.pan; @@ -404,7 +405,7 @@ export function Context(root) { context.zoomOutFurther = map.zoomOutFurther; context.redrawEnable = map.redrawEnable; - presets = presetsInit(); + presets = presetInit(); - return rebind(context, dispatch, 'on'); + return utilRebind(context, dispatch, 'on'); } diff --git a/modules/core/difference.js b/modules/core/difference.js index 281ad9c86..cffe3afac 100644 --- a/modules/core/difference.js +++ b/modules/core/difference.js @@ -1,6 +1,7 @@ import * as d3 from 'd3'; import _ from 'lodash'; + /* iD.Difference represents the difference between two graphs. It knows how to calculate the set of entities that were @@ -9,13 +10,17 @@ import _ from 'lodash'; of entities that will require a redraw, taking into account child and parent relationships. */ -export function Difference(base, head) { - var changes = {}, length = 0; +export function coreDifference(base, head) { + var changes = {}, + difference = {}, + length = 0; + function changed(h, b) { return h !== b && !_.isEqual(_.omit(h, 'v'), _.omit(b, 'v')); } + _.each(head.entities, function(h, id) { var b = base.entities[id]; if (changed(h, b)) { @@ -24,6 +29,7 @@ export function Difference(base, head) { } }); + _.each(base.entities, function(b, id) { var h = head.entities[id]; if (!changes[id] && changed(h, b)) { @@ -32,6 +38,7 @@ export function Difference(base, head) { } }); + function addParents(parents, result) { for (var i = 0; i < parents.length; i++) { var parent = parents[i]; @@ -44,16 +51,17 @@ export function Difference(base, head) { } } - var difference = {}; difference.length = function() { return length; }; + difference.changes = function() { return changes; }; + difference.extantIDs = function() { var result = []; _.each(changes, function(change, id) { @@ -62,6 +70,7 @@ export function Difference(base, head) { return result; }; + difference.modified = function() { var result = []; _.each(changes, function(change) { @@ -70,6 +79,7 @@ export function Difference(base, head) { return result; }; + difference.created = function() { var result = []; _.each(changes, function(change) { @@ -78,6 +88,7 @@ export function Difference(base, head) { return result; }; + difference.deleted = function() { var result = []; _.each(changes, function(change) { @@ -86,6 +97,7 @@ export function Difference(base, head) { return result; }; + difference.summary = function() { var relevant = {}; @@ -135,6 +147,7 @@ export function Difference(base, head) { return d3.values(relevant); }; + difference.complete = function(extent) { var result = {}, id, change; @@ -175,5 +188,6 @@ export function Difference(base, head) { return result; }; + return difference; } diff --git a/modules/core/entity.js b/modules/core/entity.js index 5fc2253d4..b4452659e 100644 --- a/modules/core/entity.js +++ b/modules/core/entity.js @@ -1,49 +1,61 @@ import _ from 'lodash'; import { debug } from '../index'; -import { interestingTag } from './tags'; -import { deprecated as deprecatedData } from '../../data/index'; +import { coreInterestingTag } from './tags'; +import { dataDeprecated } from '../../data/index'; -export function Entity(attrs) { + +export function coreEntity(attrs) { // For prototypal inheritance. - if (this instanceof Entity) return; + if (this instanceof coreEntity) return; // Create the appropriate subtype. if (attrs && attrs.type) { - return Entity[attrs.type].apply(this, arguments); + return coreEntity[attrs.type].apply(this, arguments); } else if (attrs && attrs.id) { - return Entity[Entity.id.type(attrs.id)].apply(this, arguments); + return coreEntity[coreEntity.id.type(attrs.id)].apply(this, arguments); } // Initialize a generic Entity (used only in tests). - return (new Entity()).initialize(arguments); + return (new coreEntity()).initialize(arguments); } -Entity.id = function(type) { - return Entity.id.fromOSM(type, Entity.id.next[type]--); + +coreEntity.id = function(type) { + return coreEntity.id.fromOSM(type, coreEntity.id.next[type]--); }; -Entity.id.next = {node: -1, way: -1, relation: -1}; -Entity.id.fromOSM = function(type, id) { +coreEntity.id.next = { + node: -1, way: -1, relation: -1 +}; + + +coreEntity.id.fromOSM = function(type, id) { return type[0] + id; }; -Entity.id.toOSM = function(id) { + +coreEntity.id.toOSM = function(id) { return id.slice(1); }; -Entity.id.type = function(id) { - return {'n': 'node', 'w': 'way', 'r': 'relation'}[id[0]]; + +coreEntity.id.type = function(id) { + return { 'n': 'node', 'w': 'way', 'r': 'relation' }[id[0]]; }; + // A function suitable for use as the second argument to d3.selection#data(). -Entity.key = function(entity) { +coreEntity.key = function(entity) { return entity.id + 'v' + (entity.v || 0); }; -Entity.prototype = { + +coreEntity.prototype = { + tags: {}, + initialize: function(sources) { for (var i = 0; i < sources.length; ++i) { var source = sources[i]; @@ -59,7 +71,7 @@ Entity.prototype = { } if (!this.id && this.type) { - this.id = Entity.id(this.type); + this.id = coreEntity.id(this.type); } if (!this.hasOwnProperty('visible')) { this.visible = true; @@ -77,28 +89,33 @@ Entity.prototype = { return this; }, + copy: function(resolver, copies) { if (copies[this.id]) return copies[this.id]; - var copy = Entity(this, {id: undefined, user: undefined, version: undefined}); + var copy = coreEntity(this, {id: undefined, user: undefined, version: undefined}); copies[this.id] = copy; return copy; }, + osmId: function() { - return Entity.id.toOSM(this.id); + return coreEntity.id.toOSM(this.id); }, + isNew: function() { return this.osmId() < 0; }, + update: function(attrs) { - return Entity(this, attrs, {v: 1 + (this.v || 0)}); + return coreEntity(this, attrs, {v: 1 + (this.v || 0)}); }, + mergeTags: function(tags) { var merged = _.clone(this.tags), changed = false; for (var k in tags) { @@ -115,28 +132,33 @@ Entity.prototype = { return changed ? this.update({tags: merged}) : this; }, + intersects: function(extent, resolver) { return this.extent(resolver).intersects(extent); }, + isUsed: function(resolver) { return _.without(Object.keys(this.tags), 'area').length > 0 || resolver.parentRelations(this).length > 0; }, + hasInterestingTags: function() { - return _.keys(this.tags).some(interestingTag); + return _.keys(this.tags).some(coreInterestingTag); }, + isHighwayIntersection: function() { return false; }, + deprecatedTags: function() { var tags = _.toPairs(this.tags); var deprecated = {}; - deprecatedData.forEach(function(d) { + dataDeprecated.forEach(function(d) { var match = _.toPairs(d.old)[0]; tags.forEach(function(t) { if (t[0] === match[0] && diff --git a/modules/core/graph.js b/modules/core/graph.js index 2b7bb9224..ebbcf8b57 100644 --- a/modules/core/graph.js +++ b/modules/core/graph.js @@ -1,11 +1,12 @@ import _ from 'lodash'; -import { getPrototypeOf } from '../util/index'; +import { utilGetPrototypeOf } from '../util/index'; import { debug } from '../index'; -export function Graph(other, mutable) { - if (!(this instanceof Graph)) return new Graph(other, mutable); - if (other instanceof Graph) { +export function coreGraph(other, mutable) { + if (!(this instanceof coreGraph)) return new coreGraph(other, mutable); + + if (other instanceof coreGraph) { var base = other.base(); this.entities = _.assign(Object.create(base.entities), other.entities); this._parentWays = _.assign(Object.create(base.parentWays), other._parentWays); @@ -23,11 +24,14 @@ export function Graph(other, mutable) { this.frozen = !mutable; } -Graph.prototype = { + +coreGraph.prototype = { + hasEntity: function(id) { return this.entities[id]; }, + entity: function(id) { var entity = this.entities[id]; if (!entity) { @@ -36,6 +40,7 @@ Graph.prototype = { return entity; }, + transient: function(entity, key, fn) { var id = entity.id, transients = this.transients[id] || @@ -50,6 +55,7 @@ Graph.prototype = { return transients[key]; }, + parentWays: function(entity) { var parents = this._parentWays[entity.id], result = []; @@ -62,16 +68,19 @@ Graph.prototype = { return result; }, + isPoi: function(entity) { var parentWays = this._parentWays[entity.id]; return !parentWays || parentWays.length === 0; }, + isShared: function(entity) { var parentWays = this._parentWays[entity.id]; return parentWays && parentWays.length > 1; }, + parentRelations: function(entity) { var parents = this._parentRels[entity.id], result = []; @@ -84,6 +93,7 @@ Graph.prototype = { return result; }, + childNodes: function(entity) { if (this._childNodes[entity.id]) return this._childNodes[entity.id]; if (!entity.nodes) return []; @@ -99,14 +109,16 @@ Graph.prototype = { return this._childNodes[entity.id]; }, + base: function() { return { - 'entities': getPrototypeOf(this.entities), - 'parentWays': getPrototypeOf(this._parentWays), - 'parentRels': getPrototypeOf(this._parentRels) + 'entities': utilGetPrototypeOf(this.entities), + 'parentWays': utilGetPrototypeOf(this._parentWays), + 'parentRels': utilGetPrototypeOf(this._parentRels) }; }, + // Unlike other graph methods, rebase mutates in place. This is because it // is used only during the history operation that merges newly downloaded // data into each state. To external consumers, it should appear as if the @@ -144,6 +156,7 @@ Graph.prototype = { } }, + _updateRebased: function() { var base = this.base(), i, k, child, id, keys; @@ -180,6 +193,7 @@ Graph.prototype = { // ways are always downloaded with their child nodes. }, + // Updates calculated properties (parentWays, parentRels) for the specified change _updateCalculated: function(oldentity, entity, parentWays, parentRels) { @@ -236,6 +250,7 @@ Graph.prototype = { } }, + replace: function(entity) { if (this.entities[entity.id] === entity) return this; @@ -246,6 +261,7 @@ Graph.prototype = { }); }, + remove: function(entity) { return this.update(function() { this._updateCalculated(entity, undefined); @@ -253,6 +269,7 @@ Graph.prototype = { }); }, + revert: function(id) { var baseEntity = this.base().entities[id], headEntity = this.entities[id]; @@ -266,8 +283,9 @@ Graph.prototype = { }); }, + update: function() { - var graph = this.frozen ? Graph(this, true) : this; + var graph = this.frozen ? coreGraph(this, true) : this; for (var i = 0; i < arguments.length; i++) { arguments[i].call(graph, graph); @@ -278,6 +296,7 @@ Graph.prototype = { return graph; }, + // Obliterates any existing entities load: function(entities) { var base = this.base(); diff --git a/modules/core/history.js b/modules/core/history.js index 35bc48d50..21c6c9e83 100644 --- a/modules/core/history.js +++ b/modules/core/history.js @@ -1,19 +1,21 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; import * as Validations from '../validations/index'; -import { Difference } from './difference'; -import { Entity } from './entity'; -import { Graph } from './graph'; -import { Loading } from '../ui/index'; -import { SessionMutex } from '../util/index'; -import { Tree } from './tree'; +import { coreDifference } from './difference'; +import { coreEntity } from './entity'; +import { coreGraph } from './graph'; +import { coreTree } from './tree'; +import { uiLoading } from '../ui/index'; +import { utilSessionMutex } from '../util/index'; +import { utilRebind } from '../util/rebind'; -export function History(context) { + +export function coreHistory(context) { var stack, index, tree, imageryUsed = ['Bing'], dispatch = d3.dispatch('change', 'undone', 'redone'), - lock = SessionMutex('lock'); + lock = utilSessionMutex('lock'); + function perform(actions) { actions = Array.prototype.slice.call(actions); @@ -36,26 +38,32 @@ export function History(context) { }; } + function change(previous) { - var difference = Difference(previous, history.graph()); + var difference = coreDifference(previous, history.graph()); dispatch.call('change', this, difference); return difference; } + // iD uses namespaced keys so multiple installations do not conflict function getKey(n) { return 'iD_' + window.location.origin + '_' + n; } + var history = { + graph: function() { return stack[index].graph; }, + base: function() { return stack[0].graph; }, + merge: function(entities, extent) { stack[0].graph.rebase(entities, _.map(stack, 'graph'), false); tree.rebase(entities, false); @@ -63,6 +71,7 @@ export function History(context) { dispatch.call('change', this, undefined, extent); }, + perform: function() { var previous = stack[index].graph; @@ -73,6 +82,7 @@ export function History(context) { return change(previous); }, + replace: function() { var previous = stack[index].graph; @@ -82,6 +92,7 @@ export function History(context) { return change(previous); }, + pop: function() { var previous = stack[index].graph; @@ -92,6 +103,7 @@ export function History(context) { } }, + // Same as calling pop and then perform overwrite: function() { var previous = stack[index].graph; @@ -107,6 +119,7 @@ export function History(context) { return change(previous); }, + undo: function() { var previous = stack[index].graph; @@ -120,6 +133,7 @@ export function History(context) { return change(previous); }, + redo: function() { var previous = stack[index].graph; @@ -132,6 +146,7 @@ export function History(context) { return change(previous); }, + undoAnnotation: function() { var i = index; while (i >= 0) { @@ -140,6 +155,7 @@ export function History(context) { } }, + redoAnnotation: function() { var i = index + 1; while (i <= stack.length - 1) { @@ -148,16 +164,19 @@ export function History(context) { } }, + intersects: function(extent) { return tree.intersects(extent, stack[index].graph); }, + difference: function() { var base = stack[0].graph, head = stack[index].graph; - return Difference(base, head); + return coreDifference(base, head); }, + changes: function(action) { var base = stack[0].graph, head = stack[index].graph; @@ -166,7 +185,7 @@ export function History(context) { head = action(head); } - var difference = Difference(base, head); + var difference = coreDifference(base, head); return { modified: difference.modified(), @@ -175,6 +194,7 @@ export function History(context) { }; }, + validate: function(changes) { return _(Validations) .map(function(fn) { return fn()(changes, stack[index].graph); }) @@ -182,10 +202,12 @@ export function History(context) { .value(); }, + hasChanges: function() { return this.difference().length() > 0; }, + imageryUsed: function(sources) { if (sources) { imageryUsed = sources; @@ -200,14 +222,16 @@ export function History(context) { } }, + reset: function() { - stack = [{graph: Graph()}]; + stack = [{graph: coreGraph()}]; index = 0; - tree = Tree(stack[0].graph); + tree = coreTree(stack[0].graph); dispatch.call('change'); return history; }, + toJSON: function() { if (!this.hasChanges()) return; @@ -220,7 +244,7 @@ export function History(context) { _.forEach(i.graph.entities, function(entity, id) { if (entity) { - var key = Entity.key(entity); + var key = coreEntity.key(entity); allEntities[key] = entity; modified.push(key); } else { @@ -255,23 +279,24 @@ export function History(context) { entities: _.values(allEntities), baseEntities: _.values(baseEntities), stack: s, - nextIDs: Entity.id.next, + nextIDs: coreEntity.id.next, index: index }); }, + fromJSON: function(json, loadChildNodes) { var h = JSON.parse(json), loadComplete = true; - Entity.id.next = h.nextIDs; + coreEntity.id.next = h.nextIDs; index = h.index; if (h.version === 2 || h.version === 3) { var allEntities = {}; h.entities.forEach(function(entity) { - allEntities[Entity.key(entity)] = Entity(entity); + allEntities[coreEntity.key(entity)] = Entity(entity); }); if (h.version === 3) { @@ -297,7 +322,7 @@ export function History(context) { loadComplete = false; context.redrawEnable(false); - var loading = Loading(context).blocking(true); + var loading = uiLoading(context).blocking(true); context.container().call(loading); var childNodesLoaded = function(err, result) { @@ -345,7 +370,7 @@ export function History(context) { } return { - graph: Graph(stack[0].graph).load(entities), + graph: coreGraph(stack[0].graph).load(entities), annotation: d.annotation, imageryUsed: d.imageryUsed }; @@ -360,7 +385,7 @@ export function History(context) { entities[i] = entity === 'undefined' ? undefined : Entity(entity); } - d.graph = Graph(stack[0].graph).load(entities); + d.graph = coreGraph(stack[0].graph).load(entities); return d; }); } @@ -372,31 +397,37 @@ export function History(context) { return history; }, + save: function() { if (lock.locked()) context.storage(getKey('saved_history'), history.toJSON() || null); return history; }, + clearSaved: function() { context.debouncedSave.cancel(); if (lock.locked()) context.storage(getKey('saved_history'), null); return history; }, + lock: function() { return lock.lock(); }, + unlock: function() { lock.unlock(); }, + // is iD not open in another window and it detects that // there's a history stored in localStorage that's recoverable? restorableChanges: function() { return lock.locked() && !!context.storage(getKey('saved_history')); }, + // load history from a version stored in localStorage restore: function() { if (!lock.locked()) return; @@ -405,11 +436,13 @@ export function History(context) { if (json) history.fromJSON(json, true); }, + _getKey: getKey }; + history.reset(); - return rebind(history, dispatch, 'on'); + return utilRebind(history, dispatch, 'on'); } diff --git a/modules/core/index.js b/modules/core/index.js index 1ba369b11..47e3175c3 100644 --- a/modules/core/index.js +++ b/modules/core/index.js @@ -1,11 +1,11 @@ -export { Connection } from './connection'; -export { Context } from './context'; -export { Difference } from './difference'; -export { Entity } from './entity'; -export { Graph } from './graph'; -export { History } from './history'; -export { Node } from './node'; -export { Relation } from './relation'; -export { oneWayTags, pavedTags, interestingTag } from './tags'; -export { Tree } from './tree'; -export { Way } from './way'; +export { coreConnection } from './connection'; +export { coreContext } from './context'; +export { coreDifference } from './difference'; +export { coreEntity } from './entity'; +export { coreGraph } from './graph'; +export { coreHistory } from './history'; +export { coreNode } from './node'; +export { coreRelation } from './relation'; +export { coreOneWayTags, corePavedTags, coreInterestingTag } from './tags'; +export { coreTree } from './tree'; +export { coreWay } from './way'; diff --git a/modules/core/node.js b/modules/core/node.js index 71356bb1f..0d5433e9d 100644 --- a/modules/core/node.js +++ b/modules/core/node.js @@ -1,37 +1,41 @@ import _ from 'lodash'; -// iD.Node = iD.Entity.node; -import { Entity } from './entity'; -import { Extent } from '../geo/index'; +import { coreEntity } from './entity'; +import { geoExtent } from '../geo/index'; -export function Node() { - if (!(this instanceof Node)) { - return (new Node()).initialize(arguments); +export function coreNode() { + if (!(this instanceof coreNode)) { + return (new coreNode()).initialize(arguments); } else if (arguments.length) { this.initialize(arguments); } } -Entity.node = Node; +coreEntity.node = coreNode; -Node.prototype = Object.create(Entity.prototype); +coreNode.prototype = Object.create(coreEntity.prototype); + +_.extend(coreNode.prototype, { -_.extend(Node.prototype, { type: 'node', + extent: function() { - return new Extent(this.loc); + return new geoExtent(this.loc); }, + geometry: function(graph) { return graph.transient(this, 'geometry', function() { return graph.isPoi(this) ? 'point' : 'vertex'; }); }, + move: function(loc) { return this.update({loc: loc}); }, + isIntersection: function(resolver) { return resolver.transient(this, 'isIntersection', function() { return resolver.parentWays(this).filter(function(parent) { @@ -44,6 +48,7 @@ _.extend(Node.prototype, { }); }, + isHighwayIntersection: function(resolver) { return resolver.transient(this, 'isHighwayIntersection', function() { return resolver.parentWays(this).filter(function(parent) { @@ -52,6 +57,7 @@ _.extend(Node.prototype, { }); }, + isOnAddressLine: function(resolver) { return resolver.transient(this, 'isOnAddressLine', function() { return resolver.parentWays(this).filter(function(parent) { @@ -61,6 +67,7 @@ _.extend(Node.prototype, { }); }, + asJXON: function(changeset_id) { var r = { node: { @@ -77,6 +84,7 @@ _.extend(Node.prototype, { return r; }, + asGeoJSON: function() { return { type: 'Point', diff --git a/modules/core/relation.js b/modules/core/relation.js index 972ab8f95..b02e0754e 100644 --- a/modules/core/relation.js +++ b/modules/core/relation.js @@ -1,39 +1,50 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { Extent, joinWays, polygonContainsPolygon, polygonIntersectsPolygon } from '../geo/index'; -import { Entity } from './entity'; +import { coreEntity } from './entity'; +import { + geoExtent, + geoJoinWays, + geoPolygonContainsPolygon, + geoPolygonIntersectsPolygon +} from '../geo/index'; -export function Relation() { - if (!(this instanceof Relation)) { - return (new Relation()).initialize(arguments); + +export function coreRelation() { + if (!(this instanceof coreRelation)) { + return (new coreRelation()).initialize(arguments); } else if (arguments.length) { this.initialize(arguments); } } -Entity.relation = Relation; -Relation.prototype = Object.create(Entity.prototype); -Relation.creationOrder = function(a, b) { - var aId = parseInt(Entity.id.toOSM(a.id), 10); - var bId = parseInt(Entity.id.toOSM(b.id), 10); +coreEntity.relation = coreRelation; + +coreRelation.prototype = Object.create(coreEntity.prototype); + + +coreRelation.creationOrder = function(a, b) { + var aId = parseInt(coreEntity.id.toOSM(a.id), 10); + var bId = parseInt(coreEntity.id.toOSM(b.id), 10); if (aId < 0 || bId < 0) return aId - bId; return bId - aId; }; -_.extend(Relation.prototype, { + +_.extend(coreRelation.prototype, { type: 'relation', members: [], + copy: function(resolver, copies) { if (copies[this.id]) return copies[this.id]; - var copy = Entity.prototype.copy.call(this, resolver, copies); + var copy = coreEntity.prototype.copy.call(this, resolver, copies); var members = this.members.map(function(member) { - return _.extend({}, member, {id: resolver.entity(member.id).copy(resolver, copies).id}); + return _.extend({}, member, { id: resolver.entity(member.id).copy(resolver, copies).id }); }); copy = copy.update({members: members}); @@ -42,13 +53,14 @@ _.extend(Relation.prototype, { return copy; }, + extent: function(resolver, memo) { return resolver.transient(this, 'extent', function() { - if (memo && memo[this.id]) return Extent(); + if (memo && memo[this.id]) return geoExtent(); memo = memo || {}; memo[this.id] = true; - var extent = Extent(); + var extent = geoExtent(); for (var i = 0; i < this.members.length; i++) { var member = resolver.hasEntity(this.members[i].id); if (member) { @@ -59,16 +71,19 @@ _.extend(Relation.prototype, { }); }, + geometry: function(graph) { return graph.transient(this, 'geometry', function() { return this.isMultipolygon() ? 'area' : 'relation'; }); }, + isDegenerate: function() { return this.members.length === 0; }, + // Return an array of members, each extended with an 'index' property whose value // is the member index. indexedMembers: function() { @@ -79,6 +94,7 @@ _.extend(Relation.prototype, { return result; }, + // Return the first member with the given role. A copy of the member object // is returned, extended with an 'index' property whose value is the member index. memberByRole: function(role) { @@ -89,6 +105,7 @@ _.extend(Relation.prototype, { } }, + // Return the first member with the given id. A copy of the member object // is returned, extended with an 'index' property whose value is the member index. memberById: function(id) { @@ -99,6 +116,7 @@ _.extend(Relation.prototype, { } }, + // Return the first member with the given id and role. A copy of the member object // is returned, extended with an 'index' property whose value is the member index. memberByIdAndRole: function(id, role) { @@ -109,29 +127,34 @@ _.extend(Relation.prototype, { } }, + addMember: function(member, index) { var members = this.members.slice(); members.splice(index === undefined ? members.length : index, 0, member); return this.update({members: members}); }, + updateMember: function(member, index) { var members = this.members.slice(); members.splice(index, 1, _.extend({}, members[index], member)); return this.update({members: members}); }, + removeMember: function(index) { var members = this.members.slice(); members.splice(index, 1); return this.update({members: members}); }, + removeMembersWithID: function(id) { var members = _.reject(this.members, function(m) { return m.id === id; }); return this.update({members: members}); }, + // Wherever a member appears with id `needle.id`, replace it with a member // with id `replacement.id`, type `replacement.type`, and the original role, // unless a member already exists with that id and role. Return an updated @@ -154,13 +177,20 @@ _.extend(Relation.prototype, { return this.update({members: members}); }, + asJXON: function(changeset_id) { var r = { relation: { '@id': this.osmId(), '@version': this.version || 0, member: _.map(this.members, function(member) { - return { keyAttributes: { type: member.type, role: member.role, ref: Entity.id.toOSM(member.id) } }; + return { + keyAttributes: { + type: member.type, + role: member.role, + ref: coreEntity.id.toOSM(member.id) + } + }; }), tag: _.map(this.tags, function(v, k) { return { keyAttributes: { k: k, v: v } }; @@ -171,6 +201,7 @@ _.extend(Relation.prototype, { return r; }, + asGeoJSON: function(resolver) { return resolver.transient(this, 'GeoJSON', function () { if (this.isMultipolygon()) { @@ -190,16 +221,19 @@ _.extend(Relation.prototype, { }); }, + area: function(resolver) { return resolver.transient(this, 'area', function() { return d3.geoArea(this.asGeoJSON(resolver)); }); }, + isMultipolygon: function() { return this.tags.type === 'multipolygon'; }, + isComplete: function(resolver) { for (var i = 0; i < this.members.length; i++) { if (!resolver.hasEntity(this.members[i].id)) { @@ -209,10 +243,12 @@ _.extend(Relation.prototype, { return true; }, + isRestriction: function() { return !!(this.tags.type && this.tags.type.match(/^restriction:?/)); }, + // Returns an array [A0, ... An], each Ai being an array of node arrays [Nds0, ... Ndsm], // where Nds0 is an outer ring and subsequent Ndsi's (if any i > 0) being inner rings. // @@ -227,8 +263,8 @@ _.extend(Relation.prototype, { var outers = this.members.filter(function(m) { return 'outer' === (m.role || 'outer'); }), inners = this.members.filter(function(m) { return 'inner' === m.role; }); - outers = joinWays(outers, resolver); - inners = joinWays(inners, resolver); + outers = geoJoinWays(outers, resolver); + inners = geoJoinWays(inners, resolver); outers = outers.map(function(outer) { return _.map(outer.nodes, 'loc'); }); inners = inners.map(function(inner) { return _.map(inner.nodes, 'loc'); }); @@ -236,7 +272,7 @@ _.extend(Relation.prototype, { var result = outers.map(function(o) { // Heuristic for detecting counterclockwise winding order. Assumes // that OpenStreetMap polygons are not hemisphere-spanning. - return [d3.geoArea({type: 'Polygon', coordinates: [o]}) > 2 * Math.PI ? o.reverse() : o]; + return [d3.geoArea({ type: 'Polygon', coordinates: [o] }) > 2 * Math.PI ? o.reverse() : o]; }); function findOuter(inner) { @@ -244,13 +280,13 @@ _.extend(Relation.prototype, { for (o = 0; o < outers.length; o++) { outer = outers[o]; - if (polygonContainsPolygon(outer, inner)) + if (geoPolygonContainsPolygon(outer, inner)) return o; } for (o = 0; o < outers.length; o++) { outer = outers[o]; - if (polygonIntersectsPolygon(outer, inner)) + if (geoPolygonIntersectsPolygon(outer, inner)) return o; } } @@ -258,7 +294,7 @@ _.extend(Relation.prototype, { for (var i = 0; i < inners.length; i++) { var inner = inners[i]; - if (d3.geoArea({type: 'Polygon', coordinates: [inner]}) < 2 * Math.PI) { + if (d3.geoArea({ type: 'Polygon', coordinates: [inner] }) < 2 * Math.PI) { inner = inner.reverse(); } diff --git a/modules/core/tags.js b/modules/core/tags.js index c857fe606..9bea007f1 100644 --- a/modules/core/tags.js +++ b/modules/core/tags.js @@ -1,4 +1,4 @@ -export function interestingTag(key) { +export function coreInterestingTag(key) { return key !== 'attribution' && key !== 'created_by' && key !== 'source' && @@ -7,7 +7,8 @@ export function interestingTag(key) { } -export var oneWayTags = { + +export var coreOneWayTags = { 'aerialway': { 'chair_lift': true, 'mixed_lift': true, @@ -39,7 +40,8 @@ export var oneWayTags = { } }; -export var pavedTags = { + +export var corePavedTags = { 'surface': { 'paved': true, 'asphalt': true, diff --git a/modules/core/tree.js b/modules/core/tree.js index 86a7f1f86..24decfdf6 100644 --- a/modules/core/tree.js +++ b/modules/core/tree.js @@ -1,10 +1,13 @@ import _ from 'lodash'; -import { Difference } from './difference'; +import { coreDifference } from './difference'; import rbush from 'rbush'; -export function Tree(head) { + +export function coreTree(head) { var rtree = rbush(), - bboxes = {}; + bboxes = {}, + tree = {}; + function entityBBox(entity) { var bbox = entity.extent(head).bbox(); @@ -13,6 +16,7 @@ export function Tree(head) { return bbox; } + function updateParents(entity, insertions, memo) { head.parentWays(entity).forEach(function(way) { if (bboxes[way.id]) { @@ -33,7 +37,6 @@ export function Tree(head) { }); } - var tree = {}; tree.rebase = function(entities, force) { var insertions = {}; @@ -61,9 +64,10 @@ export function Tree(head) { return tree; }; + tree.intersects = function(extent, graph) { if (graph !== head) { - var diff = Difference(head, graph), + var diff = coreDifference(head, graph), insertions = {}; head = graph; @@ -91,5 +95,6 @@ export function Tree(head) { }); }; + return tree; } diff --git a/modules/core/way.js b/modules/core/way.js index 19631b378..3ae29da12 100644 --- a/modules/core/way.js +++ b/modules/core/way.js @@ -1,45 +1,50 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { Extent, cross } from '../geo/index'; -import { Entity } from './entity'; -import { oneWayTags } from './tags'; +import { geoExtent, geoCross } from '../geo/index'; +import { coreEntity } from './entity'; +import { coreOneWayTags } from './tags'; import { areaKeys } from './context'; -export function Way() { - if (!(this instanceof Way)) { - return (new Way()).initialize(arguments); + +export function coreWay() { + if (!(this instanceof coreWay)) { + return (new coreWay()).initialize(arguments); } else if (arguments.length) { this.initialize(arguments); } } -Entity.way = Way; -Way.prototype = Object.create(Entity.prototype); +coreEntity.way = coreWay; -_.extend(Way.prototype, { +coreWay.prototype = Object.create(coreEntity.prototype); + + +_.extend(coreWay.prototype, { type: 'way', nodes: [], + copy: function(resolver, copies) { if (copies[this.id]) return copies[this.id]; - var copy = Entity.prototype.copy.call(this, resolver, copies); + var copy = coreEntity.prototype.copy.call(this, resolver, copies); var nodes = this.nodes.map(function(id) { return resolver.entity(id).copy(resolver, copies).id; }); - copy = copy.update({nodes: nodes}); + copy = copy.update({ nodes: nodes }); copies[this.id] = copy; return copy; }, + extent: function(resolver) { return resolver.transient(this, 'extent', function() { - var extent = Extent(); + var extent = geoExtent(); for (var i = 0; i < this.nodes.length; i++) { var node = resolver.hasEntity(this.nodes[i]); if (node) { @@ -50,23 +55,28 @@ _.extend(Way.prototype, { }); }, + first: function() { return this.nodes[0]; }, + last: function() { return this.nodes[this.nodes.length - 1]; }, + contains: function(node) { return this.nodes.indexOf(node) >= 0; }, + affix: function(node) { if (this.nodes[0] === node) return 'prefix'; if (this.nodes[this.nodes.length - 1] === node) return 'suffix'; }, + layer: function() { // explicit layer tag, clamp between -10, 10.. if (isFinite(this.tags.layer)) { @@ -90,6 +100,7 @@ _.extend(Way.prototype, { return 0; }, + isOneWay: function() { // explicit oneway tag.. if (['yes', '1', '-1'].indexOf(this.tags.oneway) !== -1) { return true; } @@ -97,12 +108,13 @@ _.extend(Way.prototype, { // implied oneway tag.. for (var key in this.tags) { - if (key in oneWayTags && (this.tags[key] in oneWayTags[key])) + if (key in coreOneWayTags && (this.tags[key] in coreOneWayTags[key])) return true; } return false; }, + lanes: function() { function makeLanesArray(metadata) { @@ -186,10 +198,12 @@ _.extend(Way.prototype, { }; }, + isClosed: function() { return this.nodes.length > 0 && this.first() === this.last(); }, + isConvex: function(resolver) { if (!this.isClosed() || this.isDegenerate()) return null; @@ -201,7 +215,7 @@ _.extend(Way.prototype, { var o = coords[(i+1) % coords.length], a = coords[i], b = coords[(i+2) % coords.length], - res = cross(o, a, b); + res = geoCross(o, a, b); curr = (res > 0) ? 1 : (res < 0) ? -1 : 0; if (curr === 0) { @@ -214,6 +228,7 @@ _.extend(Way.prototype, { return true; }, + isArea: function() { if (this.tags.area === 'yes') return true; @@ -227,10 +242,12 @@ _.extend(Way.prototype, { return false; }, + isDegenerate: function() { return _.uniq(this.nodes).length < (this.isArea() ? 3 : 2); }, + areAdjacent: function(n1, n2) { for (var i = 0; i < this.nodes.length; i++) { if (this.nodes[i] === n1) { @@ -241,24 +258,28 @@ _.extend(Way.prototype, { return false; }, + geometry: function(graph) { return graph.transient(this, 'geometry', function() { return this.isArea() ? 'area' : 'line'; }); }, + addNode: function(id, index) { var nodes = this.nodes.slice(); nodes.splice(index === undefined ? nodes.length : index, 0, id); return this.update({nodes: nodes}); }, + updateNode: function(id, index) { var nodes = this.nodes.slice(); nodes.splice(index, 1, id); return this.update({nodes: nodes}); }, + replaceNode: function(needle, replacement) { if (this.nodes.indexOf(needle) < 0) return this; @@ -272,6 +293,7 @@ _.extend(Way.prototype, { return this.update({nodes: nodes}); }, + removeNode: function(id) { var nodes = []; @@ -290,13 +312,14 @@ _.extend(Way.prototype, { return this.update({nodes: nodes}); }, + asJXON: function(changeset_id) { var r = { way: { '@id': this.osmId(), '@version': this.version || 0, nd: _.map(this.nodes, function(id) { - return { keyAttributes: { ref: Entity.id.toOSM(id) } }; + return { keyAttributes: { ref: coreEntity.id.toOSM(id) } }; }), tag: _.map(this.tags, function(v, k) { return { keyAttributes: { k: k, v: v } }; @@ -307,6 +330,7 @@ _.extend(Way.prototype, { return r; }, + asGeoJSON: function(resolver) { return resolver.transient(this, 'GeoJSON', function() { var coordinates = _.map(resolver.childNodes(this), 'loc'); @@ -324,6 +348,7 @@ _.extend(Way.prototype, { }); }, + area: function(resolver) { return resolver.transient(this, 'area', function() { var nodes = resolver.childNodes(this); diff --git a/modules/geo/extent.js b/modules/geo/extent.js index f5b8472ee..523d5362c 100644 --- a/modules/geo/extent.js +++ b/modules/geo/extent.js @@ -1,9 +1,13 @@ import _ from 'lodash'; -import { metersToLat, metersToLon } from './index'; +import { + geoMetersToLat, + geoMetersToLon +} from './index'; -export function Extent(min, max) { - if (!(this instanceof Extent)) return new Extent(min, max); - if (min instanceof Extent) { + +export function geoExtent(min, max) { + if (!(this instanceof geoExtent)) return new geoExtent(min, max); + if (min instanceof geoExtent) { return min; } else if (min && min.length === 2 && min[0].length === 2 && min[1].length === 2) { this[0] = min[0]; @@ -14,9 +18,10 @@ export function Extent(min, max) { } } -Extent.prototype = new Array(2); +geoExtent.prototype = new Array(2); + +_.extend(geoExtent.prototype, { -_.extend(Extent.prototype, { equals: function (obj) { return this[0][0] === obj[0][0] && this[0][1] === obj[0][1] && @@ -24,14 +29,16 @@ _.extend(Extent.prototype, { this[1][1] === obj[1][1]; }, + extend: function(obj) { - if (!(obj instanceof Extent)) obj = new Extent(obj); + if (!(obj instanceof geoExtent)) obj = new geoExtent(obj); return Extent([Math.min(obj[0][0], this[0][0]), Math.min(obj[0][1], this[0][1])], [Math.max(obj[1][0], this[1][0]), Math.max(obj[1][1], this[1][1])]); }, + _extend: function(extent) { this[0][0] = Math.min(extent[0][0], this[0][0]); this[0][1] = Math.min(extent[0][1], this[0][1]); @@ -39,23 +46,28 @@ _.extend(Extent.prototype, { this[1][1] = Math.max(extent[1][1], this[1][1]); }, + area: function() { return Math.abs((this[1][0] - this[0][0]) * (this[1][1] - this[0][1])); }, + center: function() { return [(this[0][0] + this[1][0]) / 2, (this[0][1] + this[1][1]) / 2]; }, + rectangle: function() { return [this[0][0], this[0][1], this[1][0], this[1][1]]; }, + bbox: function() { return { minX: this[0][0], minY: this[0][1], maxX: this[1][0], maxY: this[1][1] }; }, + polygon: function() { return [ [this[0][0], this[0][1]], @@ -66,32 +78,36 @@ _.extend(Extent.prototype, { ]; }, + contains: function(obj) { - if (!(obj instanceof Extent)) obj = new Extent(obj); + if (!(obj instanceof Extent)) obj = new geoExtent(obj); return obj[0][0] >= this[0][0] && obj[0][1] >= this[0][1] && obj[1][0] <= this[1][0] && obj[1][1] <= this[1][1]; }, + intersects: function(obj) { - if (!(obj instanceof Extent)) obj = new Extent(obj); + if (!(obj instanceof Extent)) obj = new geoExtent(obj); return obj[0][0] <= this[1][0] && obj[0][1] <= this[1][1] && obj[1][0] >= this[0][0] && obj[1][1] >= this[0][1]; }, + intersection: function(obj) { - if (!this.intersects(obj)) return new Extent(); + if (!this.intersects(obj)) return new geoExtent(); return new Extent([Math.max(obj[0][0], this[0][0]), Math.max(obj[0][1], this[0][1])], [Math.min(obj[1][0], this[1][0]), Math.min(obj[1][1], this[1][1])]); }, + percentContainedIn: function(obj) { - if (!(obj instanceof Extent)) obj = new Extent(obj); + if (!(obj instanceof Extent)) obj = new geoExtent(obj); var a1 = this.intersection(obj).area(), a2 = this.area(); @@ -102,14 +118,16 @@ _.extend(Extent.prototype, { } }, + padByMeters: function(meters) { - var dLat = metersToLat(meters), - dLon = metersToLon(meters, this.center()[1]); + var dLat = geoMetersToLat(meters), + dLon = geoMetersToLon(meters, this.center()[1]); return Extent( [this[0][0] - dLon, this[0][1] - dLat], [this[1][0] + dLon, this[1][1] + dLat]); }, + toParam: function() { return this.rectangle().join(','); } diff --git a/modules/geo/index.js b/modules/geo/index.js index cadd53749..bde46a325 100644 --- a/modules/geo/index.js +++ b/modules/geo/index.js @@ -1,69 +1,78 @@ import _ from 'lodash'; -export { Extent } from './extent.js'; +export { geoExtent } from './extent.js'; export { - Intersection, - Turn, - inferRestriction + geoIntersection, + geoTurn, + geoInferRestriction } from './intersection.js'; export { - isSimpleMultipolygonOuterMember, - simpleMultipolygonOuterMember, - joinWays + geoIsSimpleMultipolygonOuterMember, + geoSimpleMultipolygonOuterMember, + geoJoinWays } from './multipolygon.js'; -export { RawMercator } from './raw_mercator.js'; +export { geoRawMercator } from './raw_mercator.js'; -export function roundCoords(c) { + +export function geoRoundCoords(c) { return [Math.floor(c[0]), Math.floor(c[1])]; } -export function interp(p1, p2, t) { + +export function geoInterp(p1, p2, t) { return [p1[0] + (p2[0] - p1[0]) * t, p1[1] + (p2[1] - p1[1]) * t]; } + // 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product. // Returns a positive value, if OAB makes a counter-clockwise turn, // negative for clockwise turn, and zero if the points are collinear. -export function cross(o, a, b) { +export function geoCross(o, a, b) { return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]); } + // http://jsperf.com/id-dist-optimization -export function euclideanDistance(a, b) { +export function geoEuclideanDistance(a, b) { var x = a[0] - b[0], y = a[1] - b[1]; return Math.sqrt((x * x) + (y * y)); } + // using WGS84 polar radius (6356752.314245179 m) // const = 2 * PI * r / 360 -export function latToMeters(dLat) { +export function geoLatToMeters(dLat) { return dLat * 110946.257617; } + // using WGS84 equatorial radius (6378137.0 m) // const = 2 * PI * r / 360 -export function lonToMeters(dLon, atLat) { +export function geoLonToMeters(dLon, atLat) { return Math.abs(atLat) >= 90 ? 0 : dLon * 111319.490793 * Math.abs(Math.cos(atLat * (Math.PI/180))); } + // using WGS84 polar radius (6356752.314245179 m) // const = 2 * PI * r / 360 -export function metersToLat(m) { +export function geoMetersToLat(m) { return m / 110946.257617; } + // using WGS84 equatorial radius (6378137.0 m) // const = 2 * PI * r / 360 -export function metersToLon(m, atLat) { +export function geoMetersToLon(m, atLat) { return Math.abs(atLat) >= 90 ? 0 : m / 111319.490793 / Math.abs(Math.cos(atLat * (Math.PI/180))); } -export function offsetToMeters(offset) { + +export function geoOffsetToMeters(offset) { var equatRadius = 6356752.314245179, polarRadius = 6378137.0, tileSize = 256; @@ -74,7 +83,8 @@ export function offsetToMeters(offset) { ]; } -export function metersToOffset(meters) { + +export function geoMetersToOffset(meters) { var equatRadius = 6356752.314245179, polarRadius = 6378137.0, tileSize = 256; @@ -85,31 +95,35 @@ export function metersToOffset(meters) { ]; } + // Equirectangular approximation of spherical distances on Earth -export function sphericalDistance(a, b) { - var x = lonToMeters(a[0] - b[0], (a[1] + b[1]) / 2), - y = latToMeters(a[1] - b[1]); +export function geoSphericalDistance(a, b) { + var x = geoLonToMeters(a[0] - b[0], (a[1] + b[1]) / 2), + y = geoLatToMeters(a[1] - b[1]); return Math.sqrt((x * x) + (y * y)); } -export function edgeEqual(a, b) { + +export function geoEdgeEqual(a, b) { return (a[0] === b[0] && a[1] === b[1]) || (a[0] === b[1] && a[1] === b[0]); } + // Return the counterclockwise angle in the range (-pi, pi) // between the positive X axis and the line intersecting a and b. -export function angle(a, b, projection) { +export function geoAngle(a, b, projection) { a = projection(a.loc); b = projection(b.loc); return Math.atan2(b[1] - a[1], b[0] - a[0]); } + // Choose the edge with the minimal distance from `point` to its orthogonal // projection onto that edge, if such a projection exists, or the distance to // the closest vertex on that edge. Returns an object with the `index` of the // chosen edge, the chosen `loc` on that edge, and the `distance` to to it. -export function chooseEdge(nodes, point, projection) { +export function geoChooseEdge(nodes, point, projection) { var dist = euclideanDistance, points = nodes.map(function(n) { return projection(n.loc); }), min = Infinity, @@ -151,11 +165,12 @@ export function chooseEdge(nodes, point, projection) { }; } + // Return the intersection point of 2 line segments. // From https://github.com/pgkelley4/line-segments-intersect // This uses the vector cross product approach described below: // http://stackoverflow.com/a/565282/786339 -export function lineIntersection(a, b) { +export function geoLineIntersection(a, b) { function subtractPoints(point1, point2) { return [point1[0] - point2[0], point1[1] - point2[1]]; } @@ -184,19 +199,21 @@ export function lineIntersection(a, b) { return null; } -export function pathIntersections(path1, path2) { + +export function geoPathIntersections(path1, path2) { var intersections = []; for (var i = 0; i < path1.length - 1; i++) { for (var j = 0; j < path2.length - 1; j++) { var a = [ path1[i], path1[i+1] ], b = [ path2[j], path2[j+1] ], - hit = lineIntersection(a, b); + hit = geoLineIntersection(a, b); if (hit) intersections.push(hit); } } return intersections; } + // Return whether point is contained in polygon. // // `point` should be a 2-item array of coordinates. @@ -206,7 +223,7 @@ export function pathIntersections(path1, path2) { // ray-casting algorithm based on // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html // -export function pointInPolygon(point, polygon) { +export function geoPointInPolygon(point, polygon) { var x = point[0], y = point[1], inside = false; @@ -223,19 +240,21 @@ export function pointInPolygon(point, polygon) { return inside; } -export function polygonContainsPolygon(outer, inner) { + +export function geoPolygonContainsPolygon(outer, inner) { return _.every(inner, function(point) { - return pointInPolygon(point, outer); + return geoPointInPolygon(point, outer); }); } -export function polygonIntersectsPolygon(outer, inner, checkSegments) { + +export function geoPolygonIntersectsPolygon(outer, inner, checkSegments) { function testSegments(outer, inner) { for (var i = 0; i < outer.length - 1; i++) { for (var j = 0; j < inner.length - 1; j++) { var a = [ outer[i], outer[i+1] ], b = [ inner[j], inner[j+1] ]; - if (lineIntersection(a, b)) return true; + if (geoLineIntersection(a, b)) return true; } } return false; @@ -243,14 +262,15 @@ export function polygonIntersectsPolygon(outer, inner, checkSegments) { function testPoints(outer, inner) { return _.some(inner, function(point) { - return pointInPolygon(point, outer); + return geoPointInPolygon(point, outer); }); } return testPoints(outer, inner) || (!!checkSegments && testSegments(outer, inner)); } -export function pathLength(path) { + +export function geoPathLength(path) { var length = 0, dx, dy; for (var i = 0; i < path.length - 1; i++) { diff --git a/modules/geo/intersection.js b/modules/geo/intersection.js index 9ac7f79d7..ce4401009 100644 --- a/modules/geo/intersection.js +++ b/modules/geo/intersection.js @@ -1,14 +1,16 @@ import _ from 'lodash'; -import { Way } from '../core/index'; -import { angle as getAngle } from './index'; +import { coreWay } from '../core/index'; +import { geoAngle } from './index'; -export function Turn(turn) { - if (!(this instanceof Turn)) - return new Turn(turn); + +export function geoTurn(turn) { + if (!(this instanceof geoTurn)) + return new geoTurn(turn); _.extend(this, turn); } -export function Intersection(graph, vertexId) { + +export function geoIntersection(graph, vertexId) { var vertex = graph.entity(vertexId), parentWays = graph.parentWays(vertex), coincident = [], @@ -42,14 +44,14 @@ export function Intersection(graph, vertexId) { var splitIndex, wayA, wayB, indexA, indexB; if (isClosingNode) { splitIndex = Math.ceil(way.nodes.length / 2); // split at midpoint - wayA = Way({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, splitIndex)}); - wayB = Way({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(splitIndex)}); + wayA = coreWay({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, splitIndex)}); + wayB = coreWay({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(splitIndex)}); indexA = 1; indexB = way.nodes.length - 2; } else { splitIndex = _.indexOf(way.nodes, vertex.id, 1); // split at vertexid - wayA = Way({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, splitIndex + 1)}); - wayB = Way({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(splitIndex)}); + wayA = coreWay({id: way.id + '-a', tags: way.tags, nodes: way.nodes.slice(0, splitIndex + 1)}); + wayB = coreWay({id: way.id + '-b', tags: way.tags, nodes: way.nodes.slice(splitIndex)}); indexA = splitIndex - 1; indexB = splitIndex + 1; } @@ -72,12 +74,14 @@ export function Intersection(graph, vertexId) { graph: graph }; + intersection.adjacentNodeId = function(fromWayId) { return _.find(_.keys(highways), function(k) { return highways[k].id === fromWayId; }); }; + intersection.turns = function(fromNodeId) { var start = highways[fromNodeId]; if (!start) @@ -110,9 +114,10 @@ export function Intersection(graph, vertexId) { } }); - return Turn(turn); + return geoTurn(turn); } + var from = { node: fromNodeId, way: start.id.split(/-(a|b)/)[0] @@ -167,7 +172,7 @@ export function Intersection(graph, vertexId) { } -export function inferRestriction(graph, from, via, to, projection) { +export function geoInferRestriction(graph, from, via, to, projection) { var fromWay = graph.entity(from.way), fromNode = graph.entity(from.node), toWay = graph.entity(to.way), @@ -177,8 +182,8 @@ export function inferRestriction(graph, from, via, to, projection) { (fromWay.tags.oneway === '-1' && fromWay.first() === via.node), toOneWay = (toWay.tags.oneway === 'yes' && toWay.first() === via.node) || (toWay.tags.oneway === '-1' && toWay.last() === via.node), - angle = getAngle(viaNode, fromNode, projection) - - getAngle(viaNode, toNode, projection); + angle = geoAngle(viaNode, fromNode, projection) - + geoAngle(viaNode, toNode, projection); angle = angle * 180 / Math.PI; diff --git a/modules/geo/multipolygon.js b/modules/geo/multipolygon.js index 67b8f809c..baf558dc4 100644 --- a/modules/geo/multipolygon.js +++ b/modules/geo/multipolygon.js @@ -1,9 +1,10 @@ import _ from 'lodash'; -import { Reverse } from '../actions/reverse'; +import { actionReverse } from '../actions/reverse'; + // For fixing up rendering of multipolygons with tags on the outer member. // https://github.com/openstreetmap/iD/issues/613 -export function isSimpleMultipolygonOuterMember(entity, graph) { +export function geoIsSimpleMultipolygonOuterMember(entity, graph) { if (entity.type !== 'way') return false; @@ -27,7 +28,8 @@ export function isSimpleMultipolygonOuterMember(entity, graph) { return parent; } -export function simpleMultipolygonOuterMember(entity, graph) { + +export function geoSimpleMultipolygonOuterMember(entity, graph) { if (entity.type !== 'way') return false; @@ -52,6 +54,7 @@ export function simpleMultipolygonOuterMember(entity, graph) { return outerMember && graph.hasEntity(outerMember.id); } + // Join `array` into sequences of connecting ways. // // Segments which share identical start/end nodes will, as much as possible, @@ -72,7 +75,7 @@ export function simpleMultipolygonOuterMember(entity, graph) { // Incomplete members (those for which `graph.hasEntity(element.id)` returns // false) and non-way members are ignored. // -export function joinWays(array, graph) { +export function geoJoinWays(array, graph) { var joined = [], member, current, nodes, first, last, i, how, what; array = array.filter(function(member) { @@ -84,7 +87,7 @@ export function joinWays(array, graph) { } function reverse(member) { - return member.tags ? Reverse(member.id, {reverseOneway: true})(graph).entity(member.id) : member; + return member.tags ? actionReverse(member.id, { reverseOneway: true })(graph).entity(member.id) : member; } while (array.length) { diff --git a/modules/geo/raw_mercator.js b/modules/geo/raw_mercator.js index 2a4a84710..931e53fd4 100644 --- a/modules/geo/raw_mercator.js +++ b/modules/geo/raw_mercator.js @@ -6,28 +6,32 @@ import * as d3 from 'd3'; * Spherical rotation * Resampling */ -export function RawMercator() { +export function geoRawMercator() { var project = d3.geoMercatorRaw, k = 512 / Math.PI, // scale x = 0, y = 0, // translate clipExtent = [[0, 0], [0, 0]]; + function projection(point) { point = project(point[0] * Math.PI / 180, point[1] * Math.PI / 180); return [point[0] * k + x, y - point[1] * k]; } + projection.invert = function(point) { point = project.invert((point[0] - x) / k, (y - point[1]) / k); return point && [point[0] * 180 / Math.PI, point[1] * 180 / Math.PI]; }; + projection.scale = function(_) { if (!arguments.length) return k; k = +_; return projection; }; + projection.translate = function(_) { if (!arguments.length) return [x, y]; x = +_[0]; @@ -35,12 +39,14 @@ export function RawMercator() { return projection; }; + projection.clipExtent = function(_) { if (!arguments.length) return clipExtent; clipExtent = _; return projection; }; + projection.transform = function(_) { if (!arguments.length) return d3.zoomIdentity.translate(x, y).scale(k); x = +_.x; @@ -49,6 +55,7 @@ export function RawMercator() { return projection; }; + projection.stream = d3.geoTransform({ point: function(x, y) { x = projection([x, y]); @@ -56,5 +63,6 @@ export function RawMercator() { } }).stream; + return projection; } diff --git a/modules/index.js b/modules/index.js index 6880c1cee..a7c4c16d5 100644 --- a/modules/index.js +++ b/modules/index.js @@ -1,5 +1,6 @@ import * as actions from './actions/index'; import * as behavior from './behavior/index'; +import * as data from '../data/index.js'; import * as geo from './geo/index'; import * as modes from './modes/index'; import * as operations from './operations/index'; @@ -11,47 +12,46 @@ import * as util from './util/index'; import * as lib from './lib/index'; import * as validations from './validations/index'; -// detect -export { Detect } from './util/detect'; +export { coreConnection as Connection } from './core/connection'; +export { coreContext as Context, setAreaKeys } from './core/context'; +export { coreDifference as Difference } from './core/difference'; +export { coreEntity as Entity } from './core/entity'; +export { coreGraph as Graph } from './core/graph'; +export { coreHistory as History } from './core/history'; +export { coreNode as Node } from './core/node'; +export { coreRelation as Relation } from './core/relation'; +export { coreTree as Tree } from './core/tree'; +export { coreWay as Way } from './core/way'; +export { + coreOneWayTags as oneWayTags, + corePavedTags as pavedTags, + coreInterestingTag as interestingTag +} from './core/tags'; -// core -export { Connection } from './core/connection'; -export { Context, setAreaKeys } from './core/context'; -export { Difference } from './core/difference'; -export { Entity } from './core/entity'; -export { Graph } from './core/graph'; -export { History } from './core/history'; -export { Node } from './core/node'; -export { Relation } from './core/relation'; -export { oneWayTags, pavedTags, interestingTag } from './core/tags'; -export { Tree } from './core/tree'; -export { Way } from './core/way'; +export { rendererBackgroundSource as BackgroundSource } from './renderer/background_source'; +export { rendererBackground as Background } from './renderer/background'; +export { rendererFeatures as Features } from './renderer/features'; +export { rendererMap as Map } from './renderer/map'; +export { rendererTileLayer as TileLayer } from './renderer/tile_layer'; -// renderer -export { BackgroundSource } from './renderer/background_source'; -export { Background } from './renderer/background'; -export { Features } from './renderer/features'; -export { Map } from './renderer/map'; -export { TileLayer } from './renderer/tile_layer'; - -import * as data from '../data/index.js'; +export { utilDetect as Detect } from './util/detect'; export var debug = false; import * as d3 from 'd3'; export { - d3, - data, - actions, - geo, - behavior, - modes, - operations, - presets, - services, - svg, - util, - lib, - ui, - validations + d3, + data, + actions, + geo, + behavior, + modes, + operations, + presets, + services, + svg, + util, + lib, + ui, + validations }; diff --git a/modules/lib/d3.combobox.js b/modules/lib/d3.combobox.js index 109deb942..7b4fe95cb 100644 --- a/modules/lib/d3.combobox.js +++ b/modules/lib/d3.combobox.js @@ -1,5 +1,6 @@ -import { rebind } from '../../modules/util/rebind'; import * as d3 from 'd3'; +import { utilRebind } from '../../modules/util/rebind'; + export function d3combobox() { var event = d3.dispatch('accept'), @@ -288,9 +289,10 @@ export function d3combobox() { return combobox; }; - return rebind(combobox, event, 'on'); + return utilRebind(combobox, event, 'on'); } + d3combobox.off = function(input) { input .on('focus.typeahead', null) diff --git a/modules/modes/add_area.js b/modules/modes/add_area.js index f7f661ba6..67cb02f52 100644 --- a/modules/modes/add_area.js +++ b/modules/modes/add_area.js @@ -1,10 +1,16 @@ import { t } from '../util/locale'; -import { AddEntity, AddMidpoint, AddVertex } from '../actions/index'; -import { Node, Way } from '../core/index'; -import { AddWay } from '../behavior/index'; -import { DrawArea } from './index'; +import { + actionAddEntity, + actionAddMidpoint, + actionAddVertex +} from '../actions/index'; -export function AddArea(context) { +import { coreNode, coreWay } from '../core/index'; +import { behaviorAddWay } from '../behavior/index'; +import { modeDrawArea } from './index'; + + +export function modeAddArea(context) { var mode = { id: 'add-area', button: 'area', @@ -13,61 +19,70 @@ export function AddArea(context) { key: '3' }; - var behavior = AddWay(context) + var behavior = behaviorAddWay(context) .tail(t('modes.add_area.tail')) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode), - defaultTags = {area: 'yes'}; + defaultTags = { area: 'yes' }; + function start(loc) { var graph = context.graph(), - node = Node({loc: loc}), - way = Way({tags: defaultTags}); + node = coreNode({ loc: loc }), + way = coreWay({ tags: defaultTags }); context.perform( - AddEntity(node), - AddEntity(way), - AddVertex(way.id, node.id), - AddVertex(way.id, node.id)); + actionAddEntity(node), + actionAddEntity(way), + actionAddVertex(way.id, node.id), + actionAddVertex(way.id, node.id) + ); - context.enter(DrawArea(context, way.id, graph)); + context.enter(modeDrawArea(context, way.id, graph)); } + function startFromWay(loc, edge) { var graph = context.graph(), - node = Node({loc: loc}), - way = Way({tags: defaultTags}); + node = coreNode({ loc: loc }), + way = coreWay({ tags: defaultTags }); context.perform( - AddEntity(node), - AddEntity(way), - AddVertex(way.id, node.id), - AddVertex(way.id, node.id), - AddMidpoint({ loc: loc, edge: edge }, node)); + actionAddEntity(node), + actionAddEntity(way), + actionAddVertex(way.id, node.id), + actionAddVertex(way.id, node.id), + actionAddMidpoint({ loc: loc, edge: edge }, node) + ); - context.enter(DrawArea(context, way.id, graph)); + context.enter(modeDrawArea(context, way.id, graph)); } + function startFromNode(node) { var graph = context.graph(), - way = Way({tags: defaultTags}); + way = coreWay({ tags: defaultTags }); context.perform( - AddEntity(way), - AddVertex(way.id, node.id), - AddVertex(way.id, node.id)); + actionAddEntity(way), + actionAddVertex(way.id, node.id), + actionAddVertex(way.id, node.id) + ); - context.enter(DrawArea(context, way.id, graph)); + context.enter(modeDrawArea(context, way.id, graph)); } + mode.enter = function() { context.install(behavior); }; + mode.exit = function() { context.uninstall(behavior); }; + return mode; } diff --git a/modules/modes/add_line.js b/modules/modes/add_line.js index 393242ccf..bfdca0b2f 100644 --- a/modules/modes/add_line.js +++ b/modules/modes/add_line.js @@ -1,10 +1,16 @@ import { t } from '../util/locale'; -import { AddEntity, AddMidpoint, AddVertex } from '../actions/index'; -import { Node, Way } from '../core/index'; -import { AddWay } from '../behavior/index'; -import { DrawLine } from './index'; +import { + actionAddEntity, + actionAddMidpoint, + actionAddVertex +} from '../actions/index'; -export function AddLine(context) { +import { coreNode, coreWay } from '../core/index'; +import { behaviorAddWay } from '../behavior/index'; +import { modeDrawLine } from './index'; + + +export function modeAddLine(context) { var mode = { id: 'add-line', button: 'line', @@ -13,54 +19,62 @@ export function AddLine(context) { key: '2' }; - var behavior = AddWay(context) + var behavior = behaviorAddWay(context) .tail(t('modes.add_line.tail')) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode); + function start(loc) { var baseGraph = context.graph(), - node = Node({loc: loc}), - way = Way(); + node = coreNode({ loc: loc }), + way = coreWay(); context.perform( - AddEntity(node), - AddEntity(way), - AddVertex(way.id, node.id)); + actionAddEntity(node), + actionAddEntity(way), + actionAddVertex(way.id, node.id) + ); - context.enter(DrawLine(context, way.id, baseGraph)); + context.enter(modeDrawLine(context, way.id, baseGraph)); } + function startFromWay(loc, edge) { var baseGraph = context.graph(), - node = Node({loc: loc}), - way = Way(); + node = coreNode({ loc: loc }), + way = coreWay(); context.perform( - AddEntity(node), - AddEntity(way), - AddVertex(way.id, node.id), - AddMidpoint({ loc: loc, edge: edge }, node)); + actionAddEntity(node), + actionAddEntity(way), + actionAddVertex(way.id, node.id), + actionAddMidpoint({ loc: loc, edge: edge }, node) + ); - context.enter(DrawLine(context, way.id, baseGraph)); + context.enter(modeDrawLine(context, way.id, baseGraph)); } + function startFromNode(node) { var baseGraph = context.graph(), - way = Way(); + way = coreWay(); context.perform( - AddEntity(way), - AddVertex(way.id, node.id)); + actionAddEntity(way), + actionAddVertex(way.id, node.id) + ); - context.enter(DrawLine(context, way.id, baseGraph)); + context.enter(modeDrawLine(context, way.id, baseGraph)); } + mode.enter = function() { context.install(behavior); }; + mode.exit = function() { context.uninstall(behavior); }; diff --git a/modules/modes/add_point.js b/modules/modes/add_point.js index ad488656e..c93d861ae 100644 --- a/modules/modes/add_point.js +++ b/modules/modes/add_point.js @@ -1,10 +1,11 @@ import { t } from '../util/locale'; -import { Browse, Select } from './index'; -import { AddEntity } from '../actions/index'; -import { Draw } from '../behavior/index'; -import { Node } from '../core/index'; +import { modeBrowse, modeSelect } from './index'; +import { actionAddEntity } from '../actions/index'; +import { behaviorDraw } from '../behavior/index'; +import { coreNode } from '../core/index'; -export function AddPoint(context) { + +export function modeAddPoint(context) { var mode = { id: 'add-point', button: 'point', @@ -13,7 +14,7 @@ export function AddPoint(context) { key: '1' }; - var behavior = Draw(context) + var behavior = behavriorDraw(context) .tail(t('modes.add_point.tail')) .on('click', add) .on('clickWay', addWay) @@ -21,38 +22,45 @@ export function AddPoint(context) { .on('cancel', cancel) .on('finish', cancel); + function add(loc) { - var node = Node({loc: loc}); + var node = coreNode({ loc: loc }); context.perform( - AddEntity(node), - t('operations.add.annotation.point')); + actionAddEntity(node), + t('operations.add.annotation.point') + ); context.enter( - Select(context, [node.id]) - .suppressMenu(true) - .newFeature(true)); + modeSelect(context, [node.id]).suppressMenu(true).newFeature(true) + ); } + function addWay(loc) { add(loc); } + function addNode(node) { add(node.loc); } + function cancel() { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } + mode.enter = function() { context.install(behavior); }; + mode.exit = function() { context.uninstall(behavior); }; + return mode; } diff --git a/modules/modes/browse.js b/modules/modes/browse.js index 195a7bca6..5552d91d6 100644 --- a/modules/modes/browse.js +++ b/modules/modes/browse.js @@ -1,8 +1,16 @@ import { t } from '../util/locale'; -import { Hover, Lasso, Paste, Select } from '../behavior/index'; -import { DragNode } from './index'; -export function Browse(context) { +import { + behaviorHover, + behaviorLasso, + behaviorPaste, + behaviorSelect +} from '../behavior/index'; + +import { modeDragNode } from './index'; + + +export function modeBrowse(context) { var mode = { button: 'browse', id: 'browse', @@ -11,12 +19,13 @@ export function Browse(context) { }, sidebar; var behaviors = [ - Paste(context), - Hover(context) - .on('hover', context.ui().sidebar.hover), - Select(context), - Lasso(context), - DragNode(context).behavior]; + behaviorPaste(context), + behaviorHover(context).on('hover', context.ui().sidebar.hover), + behaviorSelect(context), + behaviorLasso(context), + modeDragNode(context).behavior + ]; + mode.enter = function() { behaviors.forEach(function(behavior) { @@ -35,6 +44,7 @@ export function Browse(context) { } }; + mode.exit = function() { context.ui().sidebar.hover.cancel(); behaviors.forEach(function(behavior) { @@ -46,11 +56,13 @@ export function Browse(context) { } }; + mode.sidebar = function(_) { if (!arguments.length) return sidebar; sidebar = _; return mode; }; + return mode; } diff --git a/modules/modes/drag_node.js b/modules/modes/drag_node.js index 6922e7f7b..d526d32ac 100644 --- a/modules/modes/drag_node.js +++ b/modules/modes/drag_node.js @@ -1,14 +1,30 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { t } from '../util/locale'; -import { AddMidpoint, Connect, MoveNode, Noop } from '../actions/index'; -import { Browse, Select } from './index'; -import { Edit, Hover, drag } from '../behavior/index'; -import { Node } from '../core/index'; -import { chooseEdge } from '../geo/index'; -import { entitySelector } from '../util/index'; +import { + actionAddMidpoint, + actionConnect, + actionMoveNode, + actionNoop +} from '../actions/index'; -export function DragNode(context) { +import { + behaviorEdit, + behaviorHover, + behaviorDrag +} from '../behavior/index'; + +import { + modeBrowse, + modeSelect +} from './index'; + +import { coreNode } from '../core/index'; +import { geoChooseEdge } from '../geo/index'; +import { utilEntitySelector } from '../util/index'; + + +export function modeDragNode(context) { var mode = { id: 'drag-node', button: 'browse' @@ -17,12 +33,11 @@ export function DragNode(context) { var nudgeInterval, activeIDs, wasMidpoint, - cancelled, + isCancelled, selectedIDs = [], - hover = Hover(context) - .altDisables(true) - .on('hover', context.ui().sidebar.hover), - edit = Edit(context); + hover = behaviorHover(context).altDisables(true).on('hover', context.ui().sidebar.hover), + edit = behaviorEdit(context); + function edge(point, size) { var pad = [30, 100, 30, 100]; @@ -33,6 +48,7 @@ export function DragNode(context) { return null; } + function startNudge(nudge) { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = window.setInterval(function() { @@ -40,33 +56,38 @@ export function DragNode(context) { }, 50); } + function stopNudge() { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = null; } + function moveAnnotation(entity) { return t('operations.move.annotation.' + entity.geometry(context.graph())); } + function connectAnnotation(entity) { return t('operations.connect.annotation.' + entity.geometry(context.graph())); } + function origin(entity) { return context.projection(entity.loc); } + function start(entity) { - cancelled = d3.event.sourceEvent.shiftKey || + isCancelled = d3.event.sourceEvent.shiftKey || context.features().hasHiddenConnections(entity, context.graph()); - if (cancelled) return behavior.cancel(); + if (isCancelled) return behavior.cancel(); wasMidpoint = entity.type === 'midpoint'; if (wasMidpoint) { var midpoint = entity; - entity = Node(); + entity = coreNode(); context.perform(AddMidpoint(midpoint, entity)); var vertex = context.surface() @@ -75,15 +96,15 @@ export function DragNode(context) { } else { context.perform( - Noop()); + actionNoop()); } activeIDs = _.map(context.graph().parentWays(entity), 'id'); activeIDs.push(entity.id); - context.enter(mode); } + function datum() { if (d3.event.sourceEvent.altKey) { return {}; @@ -92,6 +113,7 @@ export function DragNode(context) { return d3.event.sourceEvent.target.__data__ || {}; } + // via https://gist.github.com/shawnbot/4166283 function childOf(p, c) { if (p === c) return false; @@ -99,8 +121,9 @@ export function DragNode(context) { return c === p; } + function move(entity) { - if (cancelled) return; + if (isCancelled) return; d3.event.sourceEvent.stopPropagation(); var nudge = childOf(context.container().node(), @@ -116,39 +139,45 @@ export function DragNode(context) { if (d.type === 'node' && d.id !== entity.id) { loc = d.loc; } else if (d.type === 'way' && !d3.select(d3.event.sourceEvent.target).classed('fill')) { - loc = chooseEdge(context.childNodes(d), context.mouse(), context.projection).loc; + loc = geoChooseEdge(context.childNodes(d), context.mouse(), context.projection).loc; } context.replace( - MoveNode(entity.id, loc), - moveAnnotation(entity)); + actionMoveNode(entity.id, loc), + moveAnnotation(entity) + ); } + function end(entity) { - if (cancelled) return; + if (isCancelled) return; var d = datum(); if (d.type === 'way') { - var choice = chooseEdge(context.childNodes(d), context.mouse(), context.projection); + var choice = geoChooseEdge(context.childNodes(d), context.mouse(), context.projection); context.replace( - AddMidpoint({ loc: choice.loc, edge: [d.nodes[choice.index - 1], d.nodes[choice.index]] }, entity), - connectAnnotation(d)); + actionAddMidpoint({ loc: choice.loc, edge: [d.nodes[choice.index - 1], d.nodes[choice.index]] }, entity), + connectAnnotation(d) + ); } else if (d.type === 'node' && d.id !== entity.id) { context.replace( - Connect([d.id, entity.id]), - connectAnnotation(d)); + actionConnect([d.id, entity.id]), + connectAnnotation(d) + ); } else if (wasMidpoint) { context.replace( - Noop(), - t('operations.add.annotation.vertex')); + actionNoop(), + t('operations.add.annotation.vertex') + ); } else { context.replace( - Noop(), - moveAnnotation(entity)); + actionNoop(), + moveAnnotation(entity) + ); } var reselection = selectedIDs.filter(function(id) { @@ -156,25 +185,26 @@ export function DragNode(context) { }); if (reselection.length) { - context.enter( - Select(context, reselection) - .suppressMenu(true)); + context.enter(modeSelect(context, reselection).suppressMenu(true)); } else { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } } + function cancel() { behavior.cancel(); - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } + function setActiveElements() { - context.surface().selectAll(entitySelector(activeIDs)) + context.surface().selectAll(utilEntitySelector(activeIDs)) .classed('active', true); } - var behavior = drag() + + var behavior = behaviorDrag() .delegate('g.node, g.point, g.midpoint') .surface(context.surface().node()) .origin(origin) @@ -182,6 +212,7 @@ export function DragNode(context) { .on('move', move) .on('end', end); + mode.enter = function() { context.install(hover); context.install(edit); @@ -195,6 +226,7 @@ export function DragNode(context) { setActiveElements(); }; + mode.exit = function() { context.ui().sidebar.hover.cancel(); context.uninstall(hover); @@ -213,13 +245,16 @@ export function DragNode(context) { stopNudge(); }; + mode.selectedIDs = function(_) { if (!arguments.length) return selectedIDs; selectedIDs = _; return mode; }; + mode.behavior = behavior; + return mode; } diff --git a/modules/modes/draw_area.js b/modules/modes/draw_area.js index 28ca632df..763a45944 100644 --- a/modules/modes/draw_area.js +++ b/modules/modes/draw_area.js @@ -1,7 +1,7 @@ import { t } from '../util/locale'; -import { DrawWay } from '../behavior/index'; +import { behaviorDrawWay } from '../behavior/index'; -export function DrawArea(context, wayId, baseGraph) { +export function modeDrawArea(context, wayId, baseGraph) { var mode = { button: 'area', id: 'draw-area' @@ -9,12 +9,13 @@ export function DrawArea(context, wayId, baseGraph) { var behavior; + mode.enter = function() { var way = context.entity(wayId), headId = way.nodes[way.nodes.length - 2], tailId = way.first(); - behavior = DrawWay(context, wayId, -1, mode, baseGraph) + behavior = behaviorDrawWay(context, wayId, -1, mode, baseGraph) .tail(t('modes.draw_area.tail')); var addNode = behavior.addNode; @@ -30,13 +31,16 @@ export function DrawArea(context, wayId, baseGraph) { context.install(behavior); }; + mode.exit = function() { context.uninstall(behavior); }; + mode.selectedIDs = function() { return [wayId]; }; + return mode; } diff --git a/modules/modes/draw_line.js b/modules/modes/draw_line.js index 9c8691123..27058594a 100644 --- a/modules/modes/draw_line.js +++ b/modules/modes/draw_line.js @@ -1,7 +1,7 @@ import { t } from '../util/locale'; -import { DrawWay } from '../behavior/index'; +import { behaviorDrawWay } from '../behavior/index'; -export function DrawLine(context, wayId, baseGraph, affix) { +export function modeDrawLine(context, wayId, baseGraph, affix) { var mode = { button: 'line', id: 'draw-line' @@ -9,12 +9,13 @@ export function DrawLine(context, wayId, baseGraph, affix) { var behavior; + mode.enter = function() { var way = context.entity(wayId), index = (affix === 'prefix') ? 0 : undefined, headId = (affix === 'prefix') ? way.first() : way.last(); - behavior = DrawWay(context, wayId, index, mode, baseGraph) + behavior = behaviorDrawWay(context, wayId, index, mode, baseGraph) .tail(t('modes.draw_line.tail')); var addNode = behavior.addNode; @@ -30,13 +31,16 @@ export function DrawLine(context, wayId, baseGraph, affix) { context.install(behavior); }; + mode.exit = function() { context.uninstall(behavior); }; + mode.selectedIDs = function() { return [wayId]; }; + return mode; } diff --git a/modules/modes/index.js b/modules/modes/index.js index 046d6bcae..98cefa8c1 100644 --- a/modules/modes/index.js +++ b/modules/modes/index.js @@ -1,11 +1,11 @@ -export { AddArea } from './add_area'; -export { AddLine } from './add_line'; -export { AddPoint } from './add_point'; -export { Browse } from './browse'; -export { DragNode } from './drag_node'; -export { DrawArea } from './draw_area'; -export { DrawLine } from './draw_line'; -export { Move } from './move'; -export { RotateWay } from './rotate_way'; -export { Save } from './save'; -export { Select } from './select'; +export { modeAddArea } from './add_area'; +export { modeAddLine } from './add_line'; +export { modeAddPoint } from './add_point'; +export { modeBrowse } from './browse'; +export { modeDragNode } from './drag_node'; +export { modeDrawArea } from './draw_area'; +export { modeDrawLine } from './draw_line'; +export { modeMove } from './move'; +export { modeRotateWay } from './rotate_way'; +export { modeSave } from './save'; +export { modeSelect } from './select'; diff --git a/modules/modes/move.js b/modules/modes/move.js index 9e424038d..6dbff9502 100644 --- a/modules/modes/move.js +++ b/modules/modes/move.js @@ -1,18 +1,19 @@ import * as d3 from 'd3'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; -import { Browse, Select } from './index'; -import { Move as MoveAction, Noop } from '../actions/index'; -import { Edit } from '../behavior/index'; +import { modeBrowse, modeSelect } from './index'; +import { actionMove, actionNoop } from '../actions/index'; +import { behaviorEdit } from '../behavior/index'; -export function Move(context, entityIDs, baseGraph) { + +export function modeMove(context, entityIDs, baseGraph) { var mode = { id: 'move', button: 'browse' }; var keybinding = d3keybinding('move'), - edit = Edit(context), + edit = behaviorEdit(context), annotation = entityIDs.length === 1 ? t('operations.move.annotation.' + context.geometry(entityIDs[0])) : t('operations.move.annotation.multiple'), @@ -20,8 +21,10 @@ export function Move(context, entityIDs, baseGraph) { origin, nudgeInterval; + function vecSub(a, b) { return [a[0] - b[0], a[1] - b[1]]; } + function edge(point, size) { var pad = [30, 100, 30, 100]; if (point[0] > size[0] - pad[0]) return [-10, 0]; @@ -31,6 +34,7 @@ export function Move(context, entityIDs, baseGraph) { return null; } + function startNudge(nudge) { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = window.setInterval(function() { @@ -39,23 +43,25 @@ export function Move(context, entityIDs, baseGraph) { var currMouse = context.mouse(), origMouse = context.projection(origin), delta = vecSub(vecSub(currMouse, origMouse), nudge), - action = MoveAction(entityIDs, delta, context.projection, cache); + action = actionMove(entityIDs, delta, context.projection, cache); context.overwrite(action, annotation); }, 50); } + function stopNudge() { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = null; } + function move() { var currMouse = context.mouse(), origMouse = context.projection(origin), delta = vecSub(currMouse, origMouse), - action = MoveAction(entityIDs, delta, context.projection, cache); + action = actionMove(entityIDs, delta, context.projection, cache); context.overwrite(action, annotation); @@ -64,27 +70,31 @@ export function Move(context, entityIDs, baseGraph) { else stopNudge(); } + function finish() { d3.event.stopPropagation(); - context.enter(Select(context, entityIDs).suppressMenu(true)); + context.enter(modeSelect(context, entityIDs).suppressMenu(true)); stopNudge(); } + function cancel() { if (baseGraph) { while (context.graph() !== baseGraph) context.pop(); - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } else { context.pop(); - context.enter(Select(context, entityIDs).suppressMenu(true)); + context.enter(modeSelect(context, entityIDs).suppressMenu(true)); } stopNudge(); } + function undone() { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } + mode.enter = function() { origin = context.map().mouseCoordinates(); cache = {}; @@ -92,8 +102,9 @@ export function Move(context, entityIDs, baseGraph) { context.install(edit); context.perform( - Noop(), - annotation); + actionNoop(), + annotation + ); context.surface() .on('mousemove.move', move) @@ -110,6 +121,7 @@ export function Move(context, entityIDs, baseGraph) { .call(keybinding); }; + mode.exit = function() { stopNudge(); @@ -125,5 +137,6 @@ export function Move(context, entityIDs, baseGraph) { keybinding.off(); }; + return mode; } diff --git a/modules/modes/rotate_way.js b/modules/modes/rotate_way.js index 43cbe1578..80cf0147c 100644 --- a/modules/modes/rotate_way.js +++ b/modules/modes/rotate_way.js @@ -2,18 +2,20 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; -import { Browse, Select } from './index'; -import { Noop, RotateWay as RotateWayAction } from '../actions/index'; -import { Edit } from '../behavior/index'; +import { modeBrowse, modeSelect } from './index'; +import { actionNoop, actionRotateWay } from '../actions/index'; +import { behaviorEdit } from '../behavior/index'; -export function RotateWay(context, wayId) { + +export function modeRotateWay(context, wayId) { var mode = { id: 'rotate-way', button: 'browse' }; var keybinding = d3keybinding('rotate-way'), - edit = Edit(context); + edit = behaviorEdit(context); + mode.enter = function() { context.install(edit); @@ -26,38 +28,9 @@ export function RotateWay(context, wayId) { angle; context.perform( - Noop(), - annotation); - - function rotate() { - - var mousePoint = context.mouse(), - newAngle = Math.atan2(mousePoint[1] - pivot[1], mousePoint[0] - pivot[0]); - - if (typeof angle === 'undefined') angle = newAngle; - - context.replace( - RotateWayAction(wayId, pivot, newAngle - angle, context.projection), - annotation); - - angle = newAngle; - } - - function finish() { - d3.event.stopPropagation(); - context.enter(Select(context, [wayId]) - .suppressMenu(true)); - } - - function cancel() { - context.pop(); - context.enter(Select(context, [wayId]) - .suppressMenu(true)); - } - - function undone() { - context.enter(Browse(context)); - } + actionNoop(), + annotation + ); context.surface() .on('mousemove.rotate-way', rotate) @@ -72,8 +45,41 @@ export function RotateWay(context, wayId) { d3.select(document) .call(keybinding); + + + function rotate() { + var mousePoint = context.mouse(), + newAngle = Math.atan2(mousePoint[1] - pivot[1], mousePoint[0] - pivot[0]); + + if (typeof angle === 'undefined') angle = newAngle; + + context.replace( + actionRotateWay(wayId, pivot, newAngle - angle, context.projection), + annotation + ); + + angle = newAngle; + } + + + function finish() { + d3.event.stopPropagation(); + context.enter(modeSelect(context, [wayId]).suppressMenu(true)); + } + + + function cancel() { + context.pop(); + context.enter(modeSelect(context, [wayId]).suppressMenu(true)); + } + + + function undone() { + context.enter(modeBrowse(context)); + } }; + mode.exit = function() { context.uninstall(edit); @@ -87,5 +93,6 @@ export function RotateWay(context, wayId) { keybinding.off(); }; + return mode; } diff --git a/modules/modes/save.js b/modules/modes/save.js index 77d459def..410efcb20 100644 --- a/modules/modes/save.js +++ b/modules/modes/save.js @@ -1,22 +1,48 @@ import * as d3 from 'd3'; import _ from 'lodash'; + import { t } from '../util/locale'; -import { Conflicts, uiconfirm, Commit, Loading, Success } from '../ui/index'; -import { DiscardTags, MergeRemoteChanges, Noop, Revert } from '../actions/index'; -import { displayName, displayType } from '../util/index'; -import { Browse } from './index'; -import { Graph } from '../core/index'; + +import { + uiConflicts, + uiConfirm, + uiCommit, + uiLoading, + uiSuccess +} from '../ui/index'; + +import { + actionDiscardTags, + actionMergeRemoteChanges, + actionNoop, + actionRevert +} from '../actions/index'; + +import { + utilDisplayName, + utilDisplayType +} from '../util/index'; + +import { modeBrowse } from './index'; +import { coreGraph } from '../core/index'; import { JXON } from '../util/jxon'; -export function Save(context) { - var ui = Commit(context) + +export function modeSave(context) { + var mode = { + id: 'save' + }; + + var ui = uiCommit(context) .on('cancel', cancel) .on('save', save); + function cancel() { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } + function save(e, tryAgain) { function withChildNodes(ids, graph) { return _.uniq(_.reduce(ids, function(result, id) { @@ -35,18 +61,18 @@ export function Save(context) { }, _.clone(ids))); } - var loading = Loading(context).message(t('save.uploading')).blocking(true), + var loading = uiLoading(context).message(t('save.uploading')).blocking(true), history = context.history(), - origChanges = history.changes(DiscardTags(history.difference())), + origChanges = history.changes(actionDiscardTags(history.difference())), localGraph = context.graph(), - remoteGraph = Graph(history.base(), true), + remoteGraph = coreGraph(history.base(), true), modified = _.filter(history.difference().summary(), {changeType: 'modified'}), toCheck = _.map(_.map(modified, 'entity'), 'id'), toLoad = withChildNodes(toCheck, localGraph), conflicts = [], errors = []; - if (!tryAgain) history.perform(Noop()); // checkpoint + if (!tryAgain) history.perform(actionNoop()); // checkpoint context.container().call(loading); if (toCheck.length) { @@ -105,7 +131,7 @@ export function Save(context) { return '' + d + ''; } function entityName(entity) { - return displayName(entity) || (displayType(entity.id) + ' ' + entity.id); + return utilDisplayName(entity) || (utilDisplayType(entity.id) + ' ' + entity.id); } function compareVersions(local, remote) { @@ -131,7 +157,7 @@ export function Save(context) { if (compareVersions(local, remote)) return; - var action = MergeRemoteChanges, + var action = actionMergeRemoteChanges, merge = action(id, localGraph, remoteGraph, formatUser); history.replace(merge); @@ -167,7 +193,7 @@ export function Save(context) { } else if (errors.length) { showErrors(); } else { - var changes = history.changes(DiscardTags(history.difference())); + var changes = history.changes(actionDiscardTags(history.difference())); if (changes.modified.length || changes.created.length || changes.deleted.length) { context.connection().putChangeset( changes, @@ -208,7 +234,7 @@ export function Save(context) { loading.close(); - selection.call(Conflicts(context) + selection.call(uiConflicts(context) .list(conflicts) .on('download', function() { var data = JXON.stringify(context.connection().osmChangeJXON('CHANGEME', origChanges)), @@ -226,10 +252,10 @@ export function Save(context) { if (entity && entity.type === 'way') { var children = _.uniq(entity.nodes); for (var j = 0; j < children.length; j++) { - history.replace(Revert(children[j])); + history.replace(actionRevert(children[j])); } } - history.replace(Revert(conflicts[i].id)); + history.replace(actionRevert(conflicts[i].id)); } } @@ -241,7 +267,7 @@ export function Save(context) { function showErrors() { - var selection = uiconfirm(context.container()); + var selection = uiConfirm(context.container()); history.pop(); loading.close(); @@ -308,20 +334,19 @@ export function Save(context) { function success(e, changeset_id) { - context.enter(Browse(context) - .sidebar(Success(context) + context.enter(modeBrowse(context) + .sidebar(uiSuccess(context) .changeset({ id: changeset_id, comment: e.comment }) .on('cancel', function() { context.ui().sidebar.hide(); - }))); + }) + ) + ); } - var mode = { - id: 'save' - }; mode.enter = function() { context.connection().authenticate(function(err) { @@ -333,6 +358,7 @@ export function Save(context) { }); }; + mode.exit = function() { context.ui().sidebar.hide(); }; diff --git a/modules/modes/select.js b/modules/modes/select.js index e0b6186da..0a74fba82 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -2,17 +2,37 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; -import * as Operations from '../operations/index'; -import { Breathe, Copy, Hover, Lasso, Paste, Select as SelectBehavior } from '../behavior/index'; -import { Extent, chooseEdge, pointInPolygon } from '../geo/index'; -import { Node, Way } from '../core/index'; -import { RadialMenu, SelectionList } from '../ui/index'; -import { AddMidpoint } from '../actions/index'; -import { Browse } from './browse'; -import { DragNode } from './drag_node'; -import { entityOrMemberSelector } from '../util/index'; -export function Select(context, selectedIDs) { +import { actionAddMidpoint } from '../actions/index'; + +import { + behaviorBreathe, + behaviorCopy, + behaviorHover, + behaviorLasso, + behaviorPaste, + behaviorSelect +} from '../behavior/index'; + +import { + geoExtent, + geoChooseEdge, + geoPointInPolygon +} from '../geo/index'; + +import { + coreNode, + coreWay +} from '../core/index'; + +import { modeBrowse } from './browse'; +import { modeDragNode } from './drag_node'; +import * as Operations from '../operations/index'; +import { uiRadialMenu, uiSelectionList } from '../ui/index'; +import { utilEntityOrMemberSelector } from '../util/index'; + + +export function modeSelect(context, selectedIDs) { var mode = { id: 'select', button: 'browse' @@ -21,15 +41,14 @@ export function Select(context, selectedIDs) { var keybinding = d3keybinding('select'), timeout = null, behaviors = [ - Copy(context), - Paste(context), - Breathe(context), - Hover(context), - SelectBehavior(context), - Lasso(context), - DragNode(context) - .selectedIDs(selectedIDs) - .behavior], + behaviorCopy(context), + behaviorPaste(context), + behaviorBreathe(context), + behaviorHover(context), + behaviorSelect(context), + behaviorLasso(context), + modeDragNode(context).selectedIDs(selectedIDs).behavior + ], inspector, radialMenu, newFeature = false, @@ -45,12 +64,14 @@ export function Select(context, selectedIDs) { } } + function closeMenu() { if (radialMenu) { context.surface().call(radialMenu.close); } } + function positionMenu() { if (suppressMenu || !radialMenu) { return; } @@ -61,8 +82,8 @@ export function Select(context, selectedIDs) { radialMenu.center(context.projection(entity.loc)); } else { var point = context.mouse(), - viewport = Extent(context.projection.clipExtent()).polygon(); - if (pointInPolygon(point, viewport)) { + viewport = geoExtent(context.projection.clipExtent()).polygon(); + if (geoPointInPolygon(point, viewport)) { radialMenu.center(point); } else { suppressMenu = true; @@ -70,6 +91,7 @@ export function Select(context, selectedIDs) { } } + function showMenu() { closeMenu(); if (!suppressMenu && radialMenu) { @@ -77,6 +99,7 @@ export function Select(context, selectedIDs) { } } + function toggleMenu() { if (d3.select('.radial-menu').empty()) { showMenu(); @@ -85,13 +108,15 @@ export function Select(context, selectedIDs) { } } + mode.selectedIDs = function() { return selectedIDs; }; + mode.reselect = function() { var surfaceNode = context.surface().node(); - if (surfaceNode.focus) { // FF doesn't support it + if (surfaceNode.focus) { // FF doesn't support it surfaceNode.focus(); } @@ -99,47 +124,54 @@ export function Select(context, selectedIDs) { showMenu(); }; + mode.newFeature = function(_) { if (!arguments.length) return newFeature; newFeature = _; return mode; }; + mode.suppressMenu = function(_) { if (!arguments.length) return suppressMenu; suppressMenu = _; return mode; }; + mode.enter = function() { + function update() { closeMenu(); if (_.some(selectedIDs, function(id) { return !context.hasEntity(id); })) { // Exit mode if selected entity gets undone - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } } + function dblclick() { var target = d3.select(d3.event.target), datum = target.datum(); - if (datum instanceof Way && !target.classed('fill')) { - var choice = chooseEdge(context.childNodes(datum), context.mouse(), context.projection), - node = Node(); + if (datum instanceof coreWay && !target.classed('fill')) { + var choice = geoChooseEdge(context.childNodes(datum), context.mouse(), context.projection), + node = coreNode(); var prev = datum.nodes[choice.index - 1], next = datum.nodes[choice.index]; context.perform( - AddMidpoint({loc: choice.loc, edge: [prev, next]}, node), - t('operations.add.annotation.vertex')); + actionAddMidpoint({loc: choice.loc, edge: [prev, next]}, node), + t('operations.add.annotation.vertex') + ); d3.event.preventDefault(); d3.event.stopPropagation(); } } + function selectElements(drawn) { var entity = singular(); if (entity && context.geometry(entity.id) === 'relation') { @@ -148,11 +180,11 @@ export function Select(context, selectedIDs) { } var selection = context.surface() - .selectAll(entityOrMemberSelector(selectedIDs, context.graph())); + .selectAll(utilEntityOrMemberSelector(selectedIDs, context.graph())); if (selection.empty()) { if (drawn) { // Exit mode if selected DOM elements have disappeared.. - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } } else { selection @@ -160,9 +192,10 @@ export function Select(context, selectedIDs) { } } + function esc() { if (!context.inIntro()) { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } } @@ -171,11 +204,11 @@ export function Select(context, selectedIDs) { context.install(behavior); }); - var operations = _.without(d3.values(Operations), Operations.Delete) + var operations = _.without(d3.values(Operations), Operations.operationDelete) .map(function(o) { return o(selectedIDs, context); }) .filter(function(o) { return o.available(); }); - operations.unshift(Operations.Delete(selectedIDs, context)); + operations.unshift(Operations.operationDelete(selectedIDs, context)); keybinding .on('⎋', esc, true) @@ -194,7 +227,7 @@ export function Select(context, selectedIDs) { d3.select(document) .call(keybinding); - radialMenu = RadialMenu(context, operations); + radialMenu = uiRadialMenu(context, operations); context.ui().sidebar .select(singular() ? singular().id : null, newFeature); @@ -225,11 +258,12 @@ export function Select(context, selectedIDs) { }, 200); if (selectedIDs.length > 1) { - var entities = SelectionList(context, selectedIDs); + var entities = uiSelectionList(context, selectedIDs); context.ui().sidebar.show(entities); } }; + mode.exit = function() { if (timeout) window.clearTimeout(timeout); @@ -256,5 +290,6 @@ export function Select(context, selectedIDs) { context.ui().sidebar.hide(); }; + return mode; } diff --git a/modules/operations/circularize.js b/modules/operations/circularize.js index 420d7605a..475523ecb 100644 --- a/modules/operations/circularize.js +++ b/modules/operations/circularize.js @@ -1,25 +1,28 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Circularize as CircularizeAction } from '../actions/index'; +import { actionCircularize } from '../actions/index'; -export function Circularize(selectedIDs, context) { + +export function operationCircularize(selectedIDs, context) { var entityId = selectedIDs[0], entity = context.entity(entityId), extent = entity.extent(context.graph()), geometry = context.geometry(entityId), - action = CircularizeAction(entityId, context.projection); + action = actionCircularize(entityId, context.projection); var operation = function() { var annotation = t('operations.circularize.annotation.' + geometry); context.perform(action, annotation); }; + operation.available = function() { return selectedIDs.length === 1 && entity.type === 'way' && _.uniq(entity.nodes).length > 1; }; + operation.disabled = function() { var reason; if (extent.percentContainedIn(context.extent()) < 0.8) { @@ -30,6 +33,7 @@ export function Circularize(selectedIDs, context) { return action.disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -37,9 +41,11 @@ export function Circularize(selectedIDs, context) { t('operations.circularize.description.' + geometry); }; + operation.id = 'circularize'; operation.keys = [t('operations.circularize.key')]; operation.title = t('operations.circularize.title'); + return operation; } diff --git a/modules/operations/continue.js b/modules/operations/continue.js index 13758ade0..e0b286d83 100644 --- a/modules/operations/continue.js +++ b/modules/operations/continue.js @@ -1,14 +1,16 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { DrawLine } from '../modes/index'; +import { modeDrawLine } from '../modes/index'; -export function Continue(selectedIDs, context) { + +export function operationContinue(selectedIDs, context) { var graph = context.graph(), entities = selectedIDs.map(function(id) { return graph.entity(id); }), - geometries = _.extend({line: [], vertex: []}, + geometries = _.extend({ line: [], vertex: [] }, _.groupBy(entities, function(entity) { return entity.geometry(graph); })), vertex = geometries.vertex[0]; + function candidateWays() { return graph.parentWays(vertex).filter(function(parent) { return parent.geometry(graph) === 'line' && @@ -17,20 +19,21 @@ export function Continue(selectedIDs, context) { }); } + var operation = function() { var candidate = candidateWays()[0]; - context.enter(DrawLine( - context, - candidate.id, - context.graph(), - candidate.affix(vertex.id))); + context.enter( + modeDrawLine(context, candidate.id, context.graph(), candidate.affix(vertex.id)) + ); }; + operation.available = function() { return geometries.vertex.length === 1 && geometries.line.length <= 1 && !context.features().hasHiddenConnections(vertex, context.graph()); }; + operation.disabled = function() { var candidates = candidateWays(); if (candidates.length === 0) @@ -39,6 +42,7 @@ export function Continue(selectedIDs, context) { return 'multiple'; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -46,9 +50,11 @@ export function Continue(selectedIDs, context) { t('operations.continue.description'); }; + operation.id = 'continue'; operation.keys = [t('operations.continue.key')]; operation.title = t('operations.continue.title'); + return operation; } diff --git a/modules/operations/delete.js b/modules/operations/delete.js index 21644de2d..6c751978c 100644 --- a/modules/operations/delete.js +++ b/modules/operations/delete.js @@ -1,19 +1,20 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Browse, Select } from '../modes/index'; -import { DeleteMultiple } from '../actions/index'; -import { cmd } from '../ui/index'; -import { sphericalDistance } from '../geo/index'; +import { modeBrowse, modeSelect } from '../modes/index'; +import { actionDeleteMultiple } from '../actions/index'; +import { uiCmd } from '../ui/index'; +import { geoSphericalDistance } from '../geo/index'; -export function Delete(selectedIDs, context) { - var action = DeleteMultiple(selectedIDs); + +export function operationDelete(selectedIDs, context) { + var action = actionDeleteMultiple(selectedIDs); var operation = function() { var annotation, nextSelectedID; if (selectedIDs.length > 1) { - annotation = t('operations.delete.annotation.multiple', {n: selectedIDs.length}); + annotation = t('operations.delete.annotation.multiple', { n: selectedIDs.length }); } else { var id = selectedIDs[0], @@ -34,8 +35,8 @@ export function Delete(selectedIDs, context) { } else if (i === nodes.length - 1) { i--; } else { - var a = sphericalDistance(entity.loc, context.entity(nodes[i - 1]).loc), - b = sphericalDistance(entity.loc, context.entity(nodes[i + 1]).loc); + var a = geoSphericalDistance(entity.loc, context.entity(nodes[i - 1]).loc), + b = geoSphericalDistance(entity.loc, context.entity(nodes[i + 1]).loc); i = a < b ? i - 1 : i + 1; } @@ -44,20 +45,20 @@ export function Delete(selectedIDs, context) { } if (nextSelectedID && context.hasEntity(nextSelectedID)) { - context.enter(Select(context, [nextSelectedID])); + context.enter(modeSelect(context, [nextSelectedID])); } else { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } - context.perform( - action, - annotation); + context.perform(action, annotation); }; + operation.available = function() { return true; }; + operation.disabled = function() { var reason; if (_.some(selectedIDs, context.hasHiddenConnections)) { @@ -66,6 +67,7 @@ export function Delete(selectedIDs, context) { return action.disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -73,9 +75,11 @@ export function Delete(selectedIDs, context) { t('operations.delete.description'); }; + operation.id = 'delete'; - operation.keys = [cmd('⌘⌫'), cmd('⌘⌦')]; + operation.keys = [uiCmd('⌘⌫'), uiCmd('⌘⌦')]; operation.title = t('operations.delete.title'); + return operation; } diff --git a/modules/operations/disconnect.js b/modules/operations/disconnect.js index 9adcf74a4..842aa4afa 100644 --- a/modules/operations/disconnect.js +++ b/modules/operations/disconnect.js @@ -1,14 +1,15 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Disconnect as DisconnectAction } from '../actions/index'; +import { actionDisconnect } from '../actions/index'; -export function Disconnect(selectedIDs, context) { - var vertices = _.filter(selectedIDs, function vertex(entityId) { + +export function operationDisconnect(selectedIDs, context) { + var vertices = _.filter(selectedIDs, function(entityId) { return context.geometry(entityId) === 'vertex'; }); var entityId = vertices[0], - action = DisconnectAction(entityId); + action = actionDisconnect(entityId); if (selectedIDs.length > 1) { action.limitWays(_.without(selectedIDs, entityId)); @@ -18,10 +19,12 @@ export function Disconnect(selectedIDs, context) { context.perform(action, t('operations.disconnect.annotation')); }; + operation.available = function() { return vertices.length === 1; }; + operation.disabled = function() { var reason; if (_.some(selectedIDs, context.hasHiddenConnections)) { @@ -30,6 +33,7 @@ export function Disconnect(selectedIDs, context) { return action.disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -37,9 +41,11 @@ export function Disconnect(selectedIDs, context) { t('operations.disconnect.description'); }; + operation.id = 'disconnect'; operation.keys = [t('operations.disconnect.key')]; operation.title = t('operations.disconnect.title'); + return operation; } diff --git a/modules/operations/index.js b/modules/operations/index.js index 58190dbea..bd4058f5d 100644 --- a/modules/operations/index.js +++ b/modules/operations/index.js @@ -1,11 +1,11 @@ -export { Circularize } from './circularize'; -export { Continue } from './continue'; -export { Delete } from './delete'; -export { Disconnect } from './disconnect'; -export { Merge } from './merge'; -export { Move } from './move'; -export { Orthogonalize } from './orthogonalize'; -export { Reverse } from './reverse'; -export { Rotate } from './rotate'; -export { Split } from './split'; -export { Straighten } from './straighten'; +export { operationCircularize } from './circularize'; +export { operationContinue } from './continue'; +export { operationDelete } from './delete'; +export { operationDisconnect } from './disconnect'; +export { operationMerge } from './merge'; +export { operationMove } from './move'; +export { operationOrthogonalize } from './orthogonalize'; +export { operationReverse } from './reverse'; +export { operationRotate } from './rotate'; +export { operationSplit } from './split'; +export { operationStraighten } from './straighten'; diff --git a/modules/operations/merge.js b/modules/operations/merge.js index de23c445a..345440cce 100644 --- a/modules/operations/merge.js +++ b/modules/operations/merge.js @@ -1,11 +1,17 @@ import { t } from '../util/locale'; -import { Join, Merge as MergeAction, MergePolygon } from '../actions/index'; -import { Select } from '../modes/index'; +import { + actionJoin, + actionMerge, + actionMergePolygon +} from '../actions/index'; -export function Merge(selectedIDs, context) { - var join = Join(selectedIDs), - merge = MergeAction(selectedIDs), - mergePolygon = MergePolygon(selectedIDs); +import { modeSelect } from '../modes/index'; + + +export function operationMerge(selectedIDs, context) { + var join = actionJoin(selectedIDs), + merge = actionMerge(selectedIDs), + mergePolygon = actionMergePolygon(selectedIDs); var operation = function() { var annotation = t('operations.merge.annotation', {n: selectedIDs.length}), @@ -20,40 +26,49 @@ export function Merge(selectedIDs, context) { } context.perform(action, annotation); - context.enter(Select(context, selectedIDs.filter(function(id) { return context.hasEntity(id); })) - .suppressMenu(true)); + var ids = selectedIDs.filter(function(id) { return context.hasEntity(id); }); + context.enter(modeSelect(context, ids).suppressMenu(true)); }; + operation.available = function() { return selectedIDs.length >= 2; }; + operation.disabled = function() { return join.disabled(context.graph()) && merge.disabled(context.graph()) && mergePolygon.disabled(context.graph()); }; + operation.tooltip = function() { var j = join.disabled(context.graph()), m = merge.disabled(context.graph()), p = mergePolygon.disabled(context.graph()); - if (j === 'restriction' && m && p) - return t('operations.merge.restriction', {relation: context.presets().item('type/restriction').name()}); + if (j === 'restriction' && m && p) { + return t('operations.merge.restriction', + { relation: context.presets().item('type/restriction').name() }); + } - if (p === 'incomplete_relation' && j && m) + if (p === 'incomplete_relation' && j && m) { return t('operations.merge.incomplete_relation'); + } - if (j && m && p) + if (j && m && p) { return t('operations.merge.' + j); + } return t('operations.merge.description'); }; + operation.id = 'merge'; operation.keys = [t('operations.merge.key')]; operation.title = t('operations.merge.title'); + return operation; } diff --git a/modules/operations/move.js b/modules/operations/move.js index 9ef9b5e99..69a2f2a4b 100644 --- a/modules/operations/move.js +++ b/modules/operations/move.js @@ -1,23 +1,26 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Extent } from '../geo/index'; -import { Move as MoveAction } from '../actions/index'; -import { Move as MoveMode } from '../modes/index'; +import { geoExtent } from '../geo/index'; +import { actionMove } from '../actions/index'; +import { modeMove } from '../modes/index'; -export function Move(selectedIDs, context) { + +export function operationMove(selectedIDs, context) { var extent = selectedIDs.reduce(function(extent, id) { return extent.extend(context.entity(id).extent(context.graph())); - }, Extent()); + }, geoExtent()); var operation = function() { - context.enter(MoveMode(context, selectedIDs)); + context.enter(modeMove(context, selectedIDs)); }; + operation.available = function() { return selectedIDs.length > 1 || context.entity(selectedIDs[0]).type !== 'node'; }; + operation.disabled = function() { var reason; if (extent.area() && extent.percentContainedIn(context.extent()) < 0.8) { @@ -25,9 +28,11 @@ export function Move(selectedIDs, context) { } else if (_.some(selectedIDs, context.hasHiddenConnections)) { reason = 'connected_to_hidden'; } - return MoveAction(selectedIDs).disabled(context.graph()) || reason; + + return actionMove(selectedIDs).disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -35,9 +40,11 @@ export function Move(selectedIDs, context) { t('operations.move.description'); }; + operation.id = 'move'; operation.keys = [t('operations.move.key')]; operation.title = t('operations.move.title'); + return operation; } diff --git a/modules/operations/orthogonalize.js b/modules/operations/orthogonalize.js index 31bd58bd4..b8143edce 100644 --- a/modules/operations/orthogonalize.js +++ b/modules/operations/orthogonalize.js @@ -1,19 +1,21 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Orthogonalize as OrthogonalizeAction } from '../actions/index'; +import { actionOrthogonalize } from '../actions/index'; -export function Orthogonalize(selectedIDs, context) { + +export function operationOrthogonalize(selectedIDs, context) { var entityId = selectedIDs[0], entity = context.entity(entityId), extent = entity.extent(context.graph()), geometry = context.geometry(entityId), - action = OrthogonalizeAction(entityId, context.projection); + action = actionOrthogonalize(entityId, context.projection); var operation = function() { var annotation = t('operations.orthogonalize.annotation.' + geometry); context.perform(action, annotation); }; + operation.available = function() { return selectedIDs.length === 1 && entity.type === 'way' && @@ -21,6 +23,7 @@ export function Orthogonalize(selectedIDs, context) { _.uniq(entity.nodes).length > 2; }; + operation.disabled = function() { var reason; if (extent.percentContainedIn(context.extent()) < 0.8) { @@ -31,6 +34,7 @@ export function Orthogonalize(selectedIDs, context) { return action.disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -38,9 +42,11 @@ export function Orthogonalize(selectedIDs, context) { t('operations.orthogonalize.description.' + geometry); }; + operation.id = 'orthogonalize'; operation.keys = [t('operations.orthogonalize.key')]; operation.title = t('operations.orthogonalize.title'); + return operation; } diff --git a/modules/operations/reverse.js b/modules/operations/reverse.js index 65ee1c2ec..6c75df732 100644 --- a/modules/operations/reverse.js +++ b/modules/operations/reverse.js @@ -1,31 +1,38 @@ import { t } from '../util/locale'; -import { Reverse as ReverseAction } from '../actions/index'; +import { actionReverse } from '../actions/index'; -export function Reverse(selectedIDs, context) { + +export function operationReverse(selectedIDs, context) { var entityId = selectedIDs[0]; var operation = function() { context.perform( - ReverseAction(entityId), - t('operations.reverse.annotation')); + actionReverse(entityId), + t('operations.reverse.annotation') + ); }; + operation.available = function() { return selectedIDs.length === 1 && context.geometry(entityId) === 'line'; }; + operation.disabled = function() { return false; }; + operation.tooltip = function() { return t('operations.reverse.description'); }; + operation.id = 'reverse'; operation.keys = [t('operations.reverse.key')]; operation.title = t('operations.reverse.title'); + return operation; } diff --git a/modules/operations/rotate.js b/modules/operations/rotate.js index 9c4e1988a..42cadfd1c 100644 --- a/modules/operations/rotate.js +++ b/modules/operations/rotate.js @@ -1,16 +1,18 @@ import { t } from '../util/locale'; -import { RotateWay } from '../modes/index'; +import { modeRotateWay } from '../modes/index'; -export function Rotate(selectedIDs, context) { + +export function operationRotate(selectedIDs, context) { var entityId = selectedIDs[0], entity = context.entity(entityId), extent = entity.extent(context.graph()), geometry = context.geometry(entityId); var operation = function() { - context.enter(RotateWay(context, entityId)); + context.enter(modeRotateWay(context, entityId)); }; + operation.available = function() { if (selectedIDs.length !== 1 || entity.type !== 'way') return false; @@ -22,6 +24,7 @@ export function Rotate(selectedIDs, context) { return false; }; + operation.disabled = function() { if (extent.percentContainedIn(context.extent()) < 0.8) { return 'too_large'; @@ -32,6 +35,7 @@ export function Rotate(selectedIDs, context) { } }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -39,9 +43,11 @@ export function Rotate(selectedIDs, context) { t('operations.rotate.description'); }; + operation.id = 'rotate'; operation.keys = [t('operations.rotate.key')]; operation.title = t('operations.rotate.title'); + return operation; } diff --git a/modules/operations/split.js b/modules/operations/split.js index 72b11a73e..0f6aba352 100644 --- a/modules/operations/split.js +++ b/modules/operations/split.js @@ -1,20 +1,22 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Select } from '../modes/index'; -import { Split as SplitAction } from '../actions/index'; +import { modeSelect } from '../modes/index'; +import { actionSplit } from '../actions/index'; -export function Split(selectedIDs, context) { - var vertices = _.filter(selectedIDs, function vertex(entityId) { + +export function operationSplit(selectedIDs, context) { + var vertices = _.filter(selectedIDs, function(entityId) { return context.geometry(entityId) === 'vertex'; }); var entityId = vertices[0], - action = SplitAction(entityId); + action = actionSplit(entityId); if (selectedIDs.length > 1) { action.limitWays(_.without(selectedIDs, entityId)); } + var operation = function() { var annotation; @@ -26,13 +28,15 @@ export function Split(selectedIDs, context) { } var difference = context.perform(action, annotation); - context.enter(Select(context, difference.extantIDs())); + context.enter(modeSelect(context, difference.extantIDs())); }; + operation.available = function() { return vertices.length === 1; }; + operation.disabled = function() { var reason; if (_.some(selectedIDs, context.hasHiddenConnections)) { @@ -41,6 +45,7 @@ export function Split(selectedIDs, context) { return action.disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); if (disable) { @@ -55,9 +60,11 @@ export function Split(selectedIDs, context) { } }; + operation.id = 'split'; operation.keys = [t('operations.split.key')]; operation.title = t('operations.split.title'); + return operation; } diff --git a/modules/operations/straighten.js b/modules/operations/straighten.js index fd79b8cc6..e0d42cb8d 100644 --- a/modules/operations/straighten.js +++ b/modules/operations/straighten.js @@ -1,16 +1,19 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Straighten as StraightenAction } from '../actions/index'; +import { actionStraighten } from '../actions/index'; -export function Straighten(selectedIDs, context) { + +export function operationStraighten(selectedIDs, context) { var entityId = selectedIDs[0], - action = StraightenAction(entityId, context.projection); + action = actionStraighten(entityId, context.projection); + function operation() { var annotation = t('operations.straighten.annotation'); context.perform(action, annotation); } + operation.available = function() { var entity = context.entity(entityId); return selectedIDs.length === 1 && @@ -19,6 +22,7 @@ export function Straighten(selectedIDs, context) { _.uniq(entity.nodes).length > 2; }; + operation.disabled = function() { var reason; if (context.hasHiddenConnections(entityId)) { @@ -27,6 +31,7 @@ export function Straighten(selectedIDs, context) { return action.disabled(context.graph()) || reason; }; + operation.tooltip = function() { var disable = operation.disabled(); return disable ? @@ -34,9 +39,11 @@ export function Straighten(selectedIDs, context) { t('operations.straighten.description'); }; + operation.id = 'straighten'; operation.keys = [t('operations.straighten.key')]; operation.title = t('operations.straighten.title'); + return operation; } diff --git a/modules/presets/category.js b/modules/presets/category.js index 905938c8f..846e2486a 100644 --- a/modules/presets/category.js +++ b/modules/presets/category.js @@ -1,29 +1,38 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Collection } from './collection'; +import { presetCollection } from './collection'; -export function Category(id, category, all) { + +export function presetCategory(id, category, all) { category = _.clone(category); category.id = id; - category.members = Collection(category.members.map(function(id) { + + category.members = presetCollection(category.members.map(function(id) { return all.item(id); })); + category.matchGeometry = function(geometry) { return category.geometry.indexOf(geometry) >= 0; }; - category.matchScore = function() { return -1; }; + + category.matchScore = function() { + return -1; + }; + category.name = function() { return t('presets.categories.' + id + '.name', {'default': id}); }; + category.terms = function() { return []; }; + return category; } diff --git a/modules/presets/collection.js b/modules/presets/collection.js index 012872368..6fa93848d 100644 --- a/modules/presets/collection.js +++ b/modules/presets/collection.js @@ -1,7 +1,8 @@ import _ from 'lodash'; -import { editDistance } from '../util/index'; +import { utilEditDistance } from '../util/index'; -export function Collection(collection) { + +export function presetCollection(collection) { var maxSearchResults = 50, maxSuggestionResults = 10; @@ -9,18 +10,21 @@ export function Collection(collection) { collection: collection, + item: function(id) { return _.find(collection, function(d) { return d.id === id; }); }, + matchGeometry: function(geometry) { - return Collection(collection.filter(function(d) { + return presetCollection(collection.filter(function(d) { return d.matchGeometry(geometry); })); }, + search: function(value, geometry) { if (!value) return this; @@ -72,7 +76,7 @@ export function Collection(collection) { var similar_name = searchable.map(function(a) { return { preset: a, - dist: editDistance(value, a.name()) + dist: utilEditDistance(value, a.name()) }; }).filter(function(a) { return a.dist + Math.min(value.length - a.preset.name().length, 0) < 3; @@ -85,7 +89,7 @@ export function Collection(collection) { // finds close matches to value in preset.terms var similar_terms = _.filter(searchable, function(a) { return _.some(a.terms() || [], function(b) { - return editDistance(value, b) + Math.min(value.length - b.length, 0) < 3; + return utilEditDistance(value, b) + Math.min(value.length - b.length, 0) < 3; }); }); @@ -102,7 +106,7 @@ export function Collection(collection) { var similar_suggestions = suggestions.map(function(a) { return { preset: a, - dist: editDistance(value, suggestionName(a.name())) + dist: utilEditDistance(value, suggestionName(a.name())) }; }).filter(function(a) { return a.dist + Math.min(value.length - suggestionName(a.preset.name()).length, 0) < 1; @@ -123,11 +127,10 @@ export function Collection(collection) { similar_suggestions.slice(0, maxSuggestionResults) ).slice(0, maxSearchResults - 1); - return Collection(_.uniq( - results.concat(other) - )); + return presetCollection(_.uniq(results.concat(other))); } }; + return presets; } diff --git a/modules/presets/field.js b/modules/presets/field.js index f9d7e58d7..3a37e4cd6 100644 --- a/modules/presets/field.js +++ b/modules/presets/field.js @@ -1,27 +1,33 @@ import _ from 'lodash'; import { t } from '../util/locale'; -export function Field(id, field) { + +export function presetField(id, field) { field = _.clone(field); field.id = id; + field.matchGeometry = function(geometry) { return !field.geometry || field.geometry === geometry; }; + field.t = function(scope, options) { return t('presets.fields.' + id + '.' + scope, options); }; + field.label = function() { return field.t('label', {'default': id}); }; + var placeholder = field.placeholder; field.placeholder = function() { return field.t('placeholder', {'default': placeholder}); }; + return field; } diff --git a/modules/presets/index.js b/modules/presets/index.js index 9462df5f9..ba2c815f9 100644 --- a/modules/presets/index.js +++ b/modules/presets/index.js @@ -1,5 +1,5 @@ -export { Category } from './category.js'; -export { Collection } from './collection.js'; -export { Field } from './field.js'; -export { Preset } from './preset.js'; -export { presets } from './presets.js'; +export { presetCategory } from './category.js'; +export { presetCollection } from './collection.js'; +export { presetField } from './field.js'; +export { presetInit } from './init.js'; +export { presetPreset } from './preset.js'; diff --git a/modules/presets/presets.js b/modules/presets/init.js similarity index 80% rename from modules/presets/presets.js rename to modules/presets/init.js index ead783699..1a244c2db 100644 --- a/modules/presets/presets.js +++ b/modules/presets/init.js @@ -1,18 +1,19 @@ import _ from 'lodash'; -import { Category } from './category'; -import { Collection } from './collection'; -import { Field } from './field'; -import { Preset } from './preset'; +import { presetCategory } from './category'; +import { presetCollection } from './collection'; +import { presetField } from './field'; +import { presetPreset } from './preset'; -export function presets() { - // an iD.presets.Collection with methods for + +export function presetInit() { + // a presetCollection with methods for // loading new data and returning defaults - var all = Collection([]), + var all = presetCollection([]), defaults = { area: all, line: all, point: all, vertex: all, relation: all }, fields = {}, universal = [], - recent = Collection([]); + recent = presetCollection([]); // Index of presets by (geometry, tag key). var index = { @@ -99,31 +100,31 @@ export function presets() { if (d.fields) { _.forEach(d.fields, function(d, id) { - fields[id] = Field(id, d); + fields[id] = presetField(id, d); if (d.universal) universal.push(fields[id]); }); } if (d.presets) { _.forEach(d.presets, function(d, id) { - all.collection.push(Preset(id, d, fields)); + all.collection.push(presetPreset(id, d, fields)); }); } if (d.categories) { _.forEach(d.categories, function(d, id) { - all.collection.push(Category(id, d, all)); + all.collection.push(presetCategory(id, d, all)); }); } if (d.defaults) { var getItem = _.bind(all.item, all); defaults = { - area: Collection(d.defaults.area.map(getItem)), - line: Collection(d.defaults.line.map(getItem)), - point: Collection(d.defaults.point.map(getItem)), - vertex: Collection(d.defaults.vertex.map(getItem)), - relation: Collection(d.defaults.relation.map(getItem)) + area: presetCollection(d.defaults.area.map(getItem)), + line: presetCollection(d.defaults.line.map(getItem)), + point: presetCollection(d.defaults.point.map(getItem)), + vertex: presetCollection(d.defaults.vertex.map(getItem)), + relation: presetCollection(d.defaults.relation.map(getItem)) }; } @@ -153,12 +154,12 @@ export function presets() { all.defaults = function(geometry, n) { var rec = recent.matchGeometry(geometry).collection.slice(0, 4), def = _.uniq(rec.concat(defaults[geometry].collection)).slice(0, n - 1); - return Collection(_.uniq(rec.concat(def).concat(all.item(geometry)))); + return presetCollection(_.uniq(rec.concat(def).concat(all.item(geometry)))); }; all.choose = function(preset) { if (!preset.isFallback()) { - recent = Collection(_.uniq([preset].concat(recent.collection))); + recent = presetCollection(_.uniq([preset].concat(recent.collection))); } return all; }; diff --git a/modules/presets/preset.js b/modules/presets/preset.js index 50e198671..ac9eaea0c 100644 --- a/modules/presets/preset.js +++ b/modules/presets/preset.js @@ -2,21 +2,25 @@ import _ from 'lodash'; import { t } from '../util/locale'; import { areaKeys } from '../core/context'; -export function Preset(id, preset, fields) { + +export function presetPreset(id, preset, fields) { preset = _.clone(preset); preset.id = id; preset.fields = (preset.fields || []).map(getFields); preset.geometry = (preset.geometry || []); + function getFields(f) { return fields[f]; } + preset.matchGeometry = function(geometry) { return preset.geometry.indexOf(geometry) >= 0; }; + var matchScore = preset.matchScore || 1; preset.matchScore = function(entity) { var tags = preset.tags, @@ -35,10 +39,12 @@ export function Preset(id, preset, fields) { return score; }; + preset.t = function(scope, options) { return t('presets.presets.' + id + '.' + scope, options); }; + var name = preset.name || ''; preset.name = function() { if (preset.suggestion) { @@ -49,15 +55,18 @@ export function Preset(id, preset, fields) { return preset.t('name', {'default': name}); }; + preset.terms = function() { return preset.t('terms', {'default': ''}).toLowerCase().trim().split(/\s*,+\s*/); }; + preset.isFallback = function() { var tagCount = Object.keys(preset.tags).length; return tagCount === 0 || (tagCount === 1 && preset.tags.hasOwnProperty('area')); }; + preset.reference = function(geometry) { var key = Object.keys(preset.tags)[0], value = preset.tags[key]; @@ -78,6 +87,7 @@ export function Preset(id, preset, fields) { } }; + var removeTags = preset.removeTags || preset.tags; preset.removeTags = function(tags, geometry) { tags = _.omit(tags, _.keys(removeTags)); @@ -93,6 +103,7 @@ export function Preset(id, preset, fields) { return tags; }; + var applyTags = preset.addTags || preset.tags; preset.applyTags = function(tags, geometry) { var k; @@ -136,5 +147,6 @@ export function Preset(id, preset, fields) { return tags; }; + return preset; } diff --git a/modules/renderer/background.js b/modules/renderer/background.js index 9e1c25d0f..640418a7e 100644 --- a/modules/renderer/background.js +++ b/modules/renderer/background.js @@ -1,14 +1,15 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; -import { Extent, metersToOffset, offsetToMeters} from '../geo/index'; -import { qsString, stringQs } from '../util/index'; -import { BackgroundSource } from './background_source'; -import { TileLayer } from './tile_layer'; +import { utilRebind } from '../util/rebind'; +import { geoExtent, geoMetersToOffset, geoOffsetToMeters} from '../geo/index'; +import { utilQsString, utilStringQs } from '../util/index'; +import { rendererBackgroundSource } from './background_source'; +import { rendererTileLayer } from './tile_layer'; -export function Background(context) { + +export function rendererBackground(context) { var dispatch = d3.dispatch('change'), - baseLayer = TileLayer(context).projection(context.projection), + baseLayer = rendererTileLayer(context).projection(context.projection), overlayLayers = [], backgroundSources; @@ -47,11 +48,11 @@ export function Background(context) { background.updateImagery = function() { var b = background.baseLayerSource(), o = overlayLayers.map(function (d) { return d.source().id; }).join(','), - meters = offsetToMeters(b.offset()), + meters = geoOffsetToMeters(b.offset()), epsilon = 0.01, x = +meters[0].toFixed(2), y = +meters[1].toFixed(2), - q = stringQs(location.hash.substring(1)); + q = utilStringQs(location.hash.substring(1)); var id = b.id; if (id === 'custom') { @@ -76,7 +77,7 @@ export function Background(context) { delete q.offset; } - location.replace('#' + qsString(q, true)); + location.replace('#' + utilQsString(q, true)); var imageryUsed = [b.imageryUsed()]; @@ -105,12 +106,14 @@ export function Background(context) { context.history().imageryUsed(imageryUsed); }; + background.sources = function(extent) { return backgroundSources.filter(function(source) { return source.intersects(extent); }); }; + background.dimensions = function(_) { if (!_) return; baseLayer.dimensions(_); @@ -120,6 +123,7 @@ export function Background(context) { }); }; + background.baseLayerSource = function(d) { if (!arguments.length) return baseLayer.source(); baseLayer.source(d); @@ -128,20 +132,24 @@ export function Background(context) { return background; }; + background.bing = function() { background.baseLayerSource(findSource('Bing')); }; + background.showsLayer = function(d) { return d === baseLayer.source() || (d.id === 'custom' && baseLayer.source().id === 'custom') || overlayLayers.some(function(l) { return l.source() === d; }); }; + background.overlayLayerSources = function() { return overlayLayers.map(function (l) { return l.source(); }); }; + background.toggleOverlayLayer = function(d) { var layer; @@ -165,6 +173,7 @@ export function Background(context) { background.updateImagery(); }; + background.nudge = function(d, zoom) { baseLayer.source().nudge(d, zoom); dispatch.call('change'); @@ -172,6 +181,7 @@ export function Background(context) { return background; }; + background.offset = function(d) { if (!arguments.length) return baseLayer.source().offset(); baseLayer.source().offset(d); @@ -180,24 +190,25 @@ export function Background(context) { return background; }; + background.load = function(imagery) { function parseMap(qmap) { if (!qmap) return false; var args = qmap.split('/').map(Number); if (args.length < 3 || args.some(isNaN)) return false; - return Extent([args[1], args[2]]); + return geoExtent([args[1], args[2]]); } - var q = stringQs(location.hash.substring(1)), + var q = utilStringQs(location.hash.substring(1)), chosen = q.background || q.layer, extent = parseMap(q.map), best; backgroundSources = imagery.map(function(source) { if (source.type === 'bing') { - return BackgroundSource.Bing(source, dispatch); + return rendererBackgroundSource.Bing(source, dispatch); } else { - return BackgroundSource(source); + return rendererBackgroundSource(source); } }); @@ -208,7 +219,7 @@ export function Background(context) { } if (chosen && chosen.indexOf('custom:') === 0) { - background.baseLayerSource(BackgroundSource.Custom(chosen.replace(/^custom:/, ''))); + background.baseLayerSource(rendererBackgroundSource.Custom(chosen.replace(/^custom:/, ''))); } else { background.baseLayerSource(findSource(chosen) || best || findSource('Bing') || backgroundSources[1] || backgroundSources[0]); } @@ -242,10 +253,11 @@ export function Background(context) { }); if (offset.length === 2) { - background.offset(metersToOffset(offset)); + background.offset(geoMetersToOffset(offset)); } } }; - return rebind(background, dispatch, 'on'); + + return utilRebind(background, dispatch, 'on'); } diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index b460a2c2a..28d66fcc5 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -1,10 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { t } from '../util/locale'; -import { Extent, polygonIntersectsPolygon } from '../geo/index'; +import { geoExtent, geoPolygonIntersectsPolygon } from '../geo/index'; import { jsonpRequest } from '../util/jsonp_request'; -export function BackgroundSource(data) { + +export function rendererBackgroundSource(data) { var source = _.clone(data), offset = [0, 0], name = source.name, @@ -13,36 +14,43 @@ export function BackgroundSource(data) { source.scaleExtent = data.scaleExtent || [0, 20]; source.overzoom = data.overzoom !== false; + source.offset = function(_) { if (!arguments.length) return offset; offset = _; return source; }; + source.nudge = function(_, zoomlevel) { offset[0] += _[0] / Math.pow(2, zoomlevel); offset[1] += _[1] / Math.pow(2, zoomlevel); return source; }; + source.name = function() { return name; }; + source.best = function() { return best; }; + source.area = function() { if (!data.polygon) return Number.MAX_VALUE; // worldwide var area = d3.geoArea({ type: 'MultiPolygon', coordinates: [ data.polygon ] }); return isNaN(area) ? 0 : area; }; + source.imageryUsed = function() { return source.id || name; }; + source.url = function(coord) { return data.template .replace('{x}', coord[0]) @@ -67,34 +75,40 @@ export function BackgroundSource(data) { }); }; + source.intersects = function(extent) { extent = extent.polygon(); return !data.polygon || data.polygon.some(function(polygon) { - return polygonIntersectsPolygon(polygon, extent, true); + return geoPolygonIntersectsPolygon(polygon, extent, true); }); }; + source.validZoom = function(z) { return source.scaleExtent[0] <= z && (source.overzoom || source.scaleExtent[1] > z); }; + source.isLocatorOverlay = function() { return name === 'Locator Overlay'; }; + source.copyrightNotices = function() {}; + return source; } -BackgroundSource.Bing = function(data, dispatch) { + +rendererBackgroundSource.Bing = function(data, dispatch) { // http://msdn.microsoft.com/en-us/library/ff701716.aspx // http://msdn.microsoft.com/en-us/library/ff701701.aspx data.template = 'https://ecn.t{switch:0,1,2,3}.tiles.virtualearth.net/tiles/a{u}.jpeg?g=587&mkt=en-gb&n=z'; - var bing = BackgroundSource(data), + var bing = rendererBackgroundSource(data), key = 'Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU', // Same as P2 and JOSM url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' + key + '&jsonp={callback}', @@ -107,7 +121,7 @@ BackgroundSource.Bing = function(data, dispatch) { areas: provider.coverageAreas.map(function(area) { return { zoom: [area.zoomMin, area.zoomMax], - extent: Extent([area.bbox[1], area.bbox[0]], [area.bbox[3], area.bbox[2]]) + extent: geoExtent([area.bbox[1], area.bbox[0]], [area.bbox[3], area.bbox[2]]) }; }) }; @@ -134,8 +148,9 @@ BackgroundSource.Bing = function(data, dispatch) { return bing; }; -BackgroundSource.None = function() { - var source = BackgroundSource({id: 'none', template: ''}); + +rendererBackgroundSource.None = function() { + var source = rendererBackgroundSource({ id: 'none', template: '' }); source.name = function() { return t('background.none'); @@ -152,8 +167,9 @@ BackgroundSource.None = function() { return source; }; -BackgroundSource.Custom = function(template) { - var source = BackgroundSource({id: 'custom', template: template}); + +rendererBackgroundSource.Custom = function(template) { + var source = rendererBackgroundSource({ id: 'custom', template: template }); source.name = function() { return t('background.custom'); diff --git a/modules/renderer/features.js b/modules/renderer/features.js index 041c24f75..ad640069c 100644 --- a/modules/renderer/features.js +++ b/modules/renderer/features.js @@ -1,9 +1,10 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; -import { Entity } from '../core/index'; +import { utilRebind } from '../util/rebind'; +import { coreEntity } from '../core/index'; -export function Features(context) { + +export function rendererFeatures(context) { var traffic_roads = { 'motorway': true, 'motorway_link': true, @@ -55,12 +56,14 @@ export function Features(context) { _keys = [], _hidden = []; + function update() { _hidden = features.hidden(); dispatch.call('change'); dispatch.call('redraw'); } + function defineFeature(k, filter, max) { _keys.push(k); _features[k] = { @@ -170,14 +173,17 @@ export function Features(context) { function features() {} + features.features = function() { return _features; }; + features.keys = function() { return _keys; }; + features.enabled = function(k) { if (!arguments.length) { return _.filter(_keys, function(k) { return _features[k].enabled; }); @@ -185,6 +191,7 @@ export function Features(context) { return _features[k] && _features[k].enabled; }; + features.disabled = function(k) { if (!arguments.length) { return _.reject(_keys, function(k) { return _features[k].enabled; }); @@ -192,6 +199,7 @@ export function Features(context) { return _features[k] && !_features[k].enabled; }; + features.hidden = function(k) { if (!arguments.length) { return _.filter(_keys, function(k) { return _features[k].hidden(); }); @@ -199,6 +207,7 @@ export function Features(context) { return _features[k] && _features[k].hidden(); }; + features.autoHidden = function(k) { if (!arguments.length) { return _.filter(_keys, function(k) { return _features[k].autoHidden(); }); @@ -206,6 +215,7 @@ export function Features(context) { return _features[k] && _features[k].autoHidden(); }; + features.enable = function(k) { if (_features[k] && !_features[k].enabled) { _features[k].enable(); @@ -213,6 +223,7 @@ export function Features(context) { } }; + features.disable = function(k) { if (_features[k] && _features[k].enabled) { _features[k].disable(); @@ -220,6 +231,7 @@ export function Features(context) { } }; + features.toggle = function(k) { if (_features[k]) { (function(f) { return f.enabled ? f.disable() : f.enable(); }(_features[k])); @@ -227,11 +239,13 @@ export function Features(context) { } }; + features.resetStats = function() { _.each(_features, function(f) { f.count = 0; }); dispatch.call('change'); }; + features.gatherStats = function(d, resolver, dimensions) { var needsRedraw = false, type = _.groupBy(d, function(ent) { return ent.type; }), @@ -264,29 +278,34 @@ export function Features(context) { return needsRedraw; }; + features.stats = function() { _.each(_keys, function(k) { _stats[k] = _features[k].count; }); return _stats; }; + features.clear = function(d) { for (var i = 0; i < d.length; i++) { features.clearEntity(d[i]); } }; + features.clearEntity = function(entity) { - delete _cache[Entity.key(entity)]; + delete _cache[coreEntity.key(entity)]; }; + features.reset = function() { _cache = {}; }; + features.getMatches = function(entity, resolver, geometry) { if (geometry === 'vertex' || geometry === 'relation') return {}; - var ent = Entity.key(entity); + var ent = coreEntity.key(entity); if (!_cache[ent]) { _cache[ent] = {}; } @@ -312,7 +331,7 @@ export function Features(context) { if (entity.type === 'way') { var parents = features.getParents(entity, resolver, geometry); if (parents.length === 1 && parents[0].isMultipolygon()) { - var pkey = Entity.key(parents[0]); + var pkey = coreEntity.key(parents[0]); if (_cache[pkey] && _cache[pkey].matches) { matches = _.clone(_cache[pkey].matches); continue; @@ -331,10 +350,11 @@ export function Features(context) { return _cache[ent].matches; }; + features.getParents = function(entity, resolver, geometry) { if (geometry === 'point') return []; - var ent = Entity.key(entity); + var ent = coreEntity.key(entity); if (!_cache[ent]) { _cache[ent] = {}; } @@ -351,6 +371,7 @@ export function Features(context) { return _cache[ent].parents; }; + features.isHiddenFeature = function(entity, resolver, geometry) { if (!_hidden.length) return false; if (!entity.version) return false; @@ -363,6 +384,7 @@ export function Features(context) { return false; }; + features.isHiddenChild = function(entity, resolver, geometry) { if (!_hidden.length) return false; if (!entity.version || geometry === 'point') return false; @@ -378,6 +400,7 @@ export function Features(context) { return true; }; + features.hasHiddenConnections = function(entity, resolver) { if (!_hidden.length) return false; var childNodes, connections; @@ -400,6 +423,7 @@ export function Features(context) { }) : false; }; + features.isHidden = function(entity, resolver, geometry) { if (!_hidden.length) return false; if (!entity.version) return false; @@ -408,6 +432,7 @@ export function Features(context) { return fn(entity, resolver, geometry); }; + features.filter = function(d, resolver) { if (!_hidden.length) return d; @@ -421,5 +446,6 @@ export function Features(context) { return result; }; - return rebind(features, dispatch, 'on'); + + return utilRebind(features, dispatch, 'on'); } diff --git a/modules/renderer/index.js b/modules/renderer/index.js index 42ef8e0fb..ba7af8acb 100644 --- a/modules/renderer/index.js +++ b/modules/renderer/index.js @@ -1,5 +1,5 @@ -export { BackgroundSource } from './background_source'; -export { Background } from './background'; -export { Features } from './features'; -export { Map } from './map'; -export { TileLayer } from './tile_layer'; +export { rendererBackgroundSource } from './background_source'; +export { rendererBackground } from './background'; +export { rendererFeatures } from './features'; +export { rendererMap } from './map'; +export { rendererTileLayer } from './tile_layer'; diff --git a/modules/renderer/map.js b/modules/renderer/map.js index d6122cc36..9827d1f92 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -1,15 +1,33 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; import { t } from '../util/locale'; -import { bindOnce } from '../util/bind_once'; -import { getDimensions } from '../util/dimensions'; -import { Areas, Labels, Layers, Lines, Midpoints, Points, Vertices } from '../svg/index'; -import { Extent } from '../geo/index'; -import { fastMouse, setTransform, functor } from '../util/index'; -import { flash } from '../ui/index'; -export function Map(context) { +import { utilRebind } from '../util/rebind'; +import { utilBindOnce } from '../util/bind_once'; +import { utilGetDimensions } from '../util/dimensions'; + +import { + svgAreas, + svgLabels, + svgLayers, + svgLines, + svgMidpoints, + svgPoints, + svgVertices +} from '../svg/index'; + +import { geoExtent } from '../geo/index'; + +import { + utilFastMouse, + utilSetTransform, + utilFunctor +} from '../util/index'; + +import { uiFlash } from '../ui/index'; + + +export function rendererMap(context) { var dimensions = [1, 1], dispatch = d3.dispatch('move', 'drawn'), @@ -19,13 +37,13 @@ export function Map(context) { transformStart = projection.transform(), transformed = false, minzoom = 0, - drawLayers = Layers(projection, context), - drawPoints = Points(projection, context), - drawVertices = Vertices(projection, context), - drawLines = Lines(projection), - drawAreas = Areas(projection, context), - drawMidpoints = Midpoints(projection, context), - drawLabels = Labels(projection, context), + drawLayers = svgLayers(projection, context), + drawPoints = svgPoints(projection, context), + drawVertices = svgVertices(projection, context), + drawLines = svgLines(projection), + drawAreas = svgAreas(projection, context), + drawMidpoints = svgMidpoints(projection, context), + drawLabels = svgLabels(projection, context), supersurface, wrapper, surface, @@ -64,7 +82,7 @@ export function Map(context) { supersurface = selection.append('div') .attr('id', 'supersurface') - .call(setTransform, 0, 0); + .call(utilSetTransform, 0, 0); // Need a wrapper div because Opera can't cope with an absolutely positioned // SVG element: http://bl.ocks.org/jfirebaugh/6fbfbd922552bf776c16 @@ -112,7 +130,7 @@ export function Map(context) { context.on('enter.map', function() { if (map.editable() && !transformed) { var all = context.intersects(map.extent()), - filter = functor(true), + filter = utilFunctor(true), graph = context.graph(); all = context.features().filter(all, graph); @@ -123,7 +141,7 @@ export function Map(context) { } }); - map.dimensions(getDimensions(selection)); + map.dimensions(utilGetDimensions(selection)); drawLabels.supersurface(supersurface); } @@ -168,7 +186,7 @@ export function Map(context) { } else { data = all; - filter = functor(true); + filter = utilFunctor(true); } } @@ -212,7 +230,7 @@ export function Map(context) { if (ktoz(eventTransform.k * 2 * Math.PI) < minzoom) { surface.interrupt(); - flash(context.container()) + uiFlash(context.container()) .select('.content') .text(t('cannot_zoom')); setZoom(context.minEditableZoom(), true); @@ -228,7 +246,7 @@ export function Map(context) { tY = (eventTransform.y / scale - transformStart.y) * scale; transformed = true; - setTransform(supersurface, tX, tY, scale); + utilSetTransform(supersurface, tX, tY, scale); queueRedraw(); dispatch.call('move', this, map); @@ -239,7 +257,7 @@ export function Map(context) { if (!transformed) return false; surface.selectAll('.radial-menu').interrupt().remove(); - setTransform(supersurface, 0, 0); + utilSetTransform(supersurface, 0, 0); transformed = false; return true; } @@ -427,7 +445,7 @@ export function Map(context) { drawLayers.dimensions(dimensions); context.background().dimensions(dimensions); projection.clipExtent([[0, 0], dimensions]); - mouse = fastMouse(supersurface.node()); + mouse = utilFastMouse(supersurface.node()); setCenter(center); return redraw(); @@ -469,7 +487,7 @@ export function Map(context) { if (z2 < minzoom) { surface.interrupt(); - flash(context.container()) + uiFlash(context.container()) .select('.content') .text(t('cannot_zoom')); z2 = context.minEditableZoom(); @@ -520,7 +538,7 @@ export function Map(context) { map.startEase = function() { - bindOnce(surface, 'mousedown.ease', function() { + utilBindOnce(surface, 'mousedown.ease', function() { map.cancelEase(); }); return map; @@ -535,10 +553,10 @@ export function Map(context) { map.extent = function(_) { if (!arguments.length) { - return new Extent(projection.invert([0, dimensions[1]]), + return new geoExtent(projection.invert([0, dimensions[1]]), projection.invert([dimensions[0], 0])); } else { - var extent = Extent(_); + var extent = geoExtent(_); map.centerZoom(extent.center(), map.extentZoom(extent)); } }; @@ -547,10 +565,10 @@ export function Map(context) { map.trimmedExtent = function(_) { if (!arguments.length) { var headerY = 60, footerY = 30, pad = 10; - return new Extent(projection.invert([pad, dimensions[1] - footerY - pad]), - projection.invert([dimensions[0] - pad, headerY + pad])); + return new geoExtent(projection.invert([pad, dimensions[1] - footerY - pad]), + projection.invert([dimensions[0] - pad, headerY + pad])); } else { - var extent = Extent(_); + var extent = geoExtent(_); map.centerZoom(extent.center(), map.trimmedExtentZoom(extent)); } }; @@ -572,14 +590,14 @@ export function Map(context) { map.extentZoom = function(_) { - return calcZoom(Extent(_), dimensions); + return calcZoom(geoExtent(_), dimensions); }; map.trimmedExtentZoom = function(_) { var trimY = 120, trimX = 40, trimmed = [dimensions[0] - trimX, dimensions[1] - trimY]; - return calcZoom(Extent(_), trimmed); + return calcZoom(geoExtent(_), trimmed); }; @@ -597,5 +615,6 @@ export function Map(context) { map.layers = drawLayers; - return rebind(map, dispatch, 'on'); + + return utilRebind(map, dispatch, 'on'); } diff --git a/modules/renderer/tile_layer.js b/modules/renderer/tile_layer.js index cf343164d..8f17930d9 100644 --- a/modules/renderer/tile_layer.js +++ b/modules/renderer/tile_layer.js @@ -1,17 +1,18 @@ import * as d3 from 'd3'; import { d3geoTile } from '../lib/d3.geo.tile'; -import { prefixCSSProperty } from '../util/index'; -import { BackgroundSource } from './background_source.js'; +import { utilPrefixCSSProperty } from '../util/index'; +import { rendererBackgroundSource } from './background_source.js'; -export function TileLayer(context) { + +export function rendererTileLayer(context) { var tileSize = 256, tile = d3geoTile(), projection, cache = {}, tileOrigin, z, - transformProp = prefixCSSProperty('Transform'), - source = BackgroundSource.None(); + transformProp = utilPrefixCSSProperty('Transform'), + source = rendererBackgroundSource.None(); // blacklist overlay tiles around Null Island.. diff --git a/modules/services/index.js b/modules/services/index.js index b549af20d..2e9d38d30 100644 --- a/modules/services/index.js +++ b/modules/services/index.js @@ -1,7 +1,13 @@ -import * as mapillary from './mapillary'; -import * as nominatim from './nominatim'; -import * as taginfo from './taginfo'; -import * as wikidata from './wikidata'; -import * as wikipedia from './wikipedia'; +import * as serviceMapillary from './mapillary'; +import * as serviceNominatim from './nominatim'; +import * as serviceTaginfo from './taginfo'; +import * as serviceWikidata from './wikidata'; +import * as serviceWikipedia from './wikipedia'; -export { mapillary, taginfo, nominatim, wikidata, wikipedia}; +export { + serviceMapillary, + serviceTaginfo, + serviceNominatim, + serviceWikidata, + serviceWikipedia +}; diff --git a/modules/services/mapillary.js b/modules/services/mapillary.js index 38c23b08e..657bba71d 100644 --- a/modules/services/mapillary.js +++ b/modules/services/mapillary.js @@ -2,12 +2,12 @@ import * as d3 from 'd3'; import _ from 'lodash'; import rbush from 'rbush'; -import { rebind } from '../util/rebind'; +import { utilRebind } from '../util/rebind'; import { d3geoTile } from '../lib/d3.geo.tile'; -import { Detect } from '../util/detect'; -import { Extent } from '../geo/index'; -import { Icon } from '../svg/index'; -import { qsString } from '../util/index'; +import { utilDetect } from '../util/detect'; +import { geoExtent } from '../geo/index'; +import { svgIcon } from '../svg/index'; +import { utilQsString } from '../util/index'; var mapillary = {}, @@ -20,6 +20,7 @@ var mapillary = {}, tileZoom = 14, dispatch = d3.dispatch('loadedImages', 'loadedSigns'); + function loadSignStyles(context) { d3.select('head').selectAll('#traffico') .data([0]) @@ -30,6 +31,7 @@ function loadSignStyles(context) { .attr('href', context.asset('traffico/stylesheets/traffico.css')); } + function loadSignDefs(context) { if (mapillary.sign_defs) return; mapillary.sign_defs = {}; @@ -43,6 +45,7 @@ function loadSignDefs(context) { }); } + function loadViewer() { // mapillary-wrap var wrap = d3.select('#content').selectAll('.mapillary-wrap') @@ -59,7 +62,7 @@ function loadViewer() { .attr('class', 'thumb-hide') .on('click', function () { mapillary.hideViewer(); }) .append('div') - .call(Icon('#icon-close')); + .call(svgIcon('#icon-close')); enter .append('div') @@ -85,6 +88,7 @@ function loadViewer() { .attr('src', viewerjs); } + function initViewer(imageKey, context) { function nodeChanged(d) { @@ -117,10 +121,12 @@ function initViewer(imageKey, context) { } } + function abortRequest(i) { i.abort(); } + function nearNullIsland(x, y, z) { if (z >= 7) { var center = Math.pow(2, z - 1), @@ -132,6 +138,7 @@ function nearNullIsland(x, y, z) { return false; } + function getTiles(projection, dimensions) { var s = projection.scale() * 2 * Math.PI, z = Math.max(Math.log(s) / Math.log(2) - 8, 0), @@ -151,13 +158,15 @@ function getTiles(projection, dimensions) { return { id: tile.toString(), - extent: Extent( + extent: geoExtent( projection.invert([x, y + ts]), - projection.invert([x + ts, y])) + projection.invert([x + ts, y]) + ) }; }); } + function loadTiles(which, url, projection, dimensions) { var tiles = getTiles(projection, dimensions).filter(function(t) { var xyz = t.id.split(','); @@ -175,6 +184,7 @@ function loadTiles(which, url, projection, dimensions) { }); } + function loadTilePage(which, url, tile, page) { var cache = mapillary.cache[which], id = tile.id + ',' + String(page), @@ -183,7 +193,7 @@ function loadTilePage(which, url, tile, page) { if (cache.loaded[id] || cache.inflight[id]) return; cache.inflight[id] = d3.json(url + - qsString({ + utilQsString({ geojson: 'true', limit: maxResults, page: page, @@ -223,6 +233,7 @@ function loadTilePage(which, url, tile, page) { ); } + // partition viewport into `psize` x `psize` regions function partitionViewport(psize, projection, dimensions) { psize = psize || 16; @@ -235,13 +246,14 @@ function partitionViewport(psize, projection, dimensions) { var min = [x, y + psize], max = [x + psize, y]; partitions.push( - Extent(projection.invert(min), projection.invert(max))); + geoExtent(projection.invert(min), projection.invert(max))); }); }); return partitions; } + // no more than `limit` results per partition. function searchLimited(psize, limit, projection, dimensions, rtree) { limit = limit || 3; @@ -254,11 +266,13 @@ function searchLimited(psize, limit, projection, dimensions, rtree) { }))); } + // this function is only used by test cases export function getMapillary() { return mapillary; } + export function init() { mapillary.loadImages = function(projection, dimensions) { @@ -293,7 +307,7 @@ export function init() { mapillary.signsSupported = function() { - var detected = Detect(); + var detected = utilDetect(); return (!(detected.ie || detected.browser.toLowerCase() === 'safari')); }; @@ -423,7 +437,7 @@ export function init() { } - mapillary.event = rebind(mapillary, dispatch, 'on'); + mapillary.event = utilRebind(mapillary, dispatch, 'on'); return mapillary; } diff --git a/modules/services/nominatim.js b/modules/services/nominatim.js index 70a3e5e3c..950be8ab3 100644 --- a/modules/services/nominatim.js +++ b/modules/services/nominatim.js @@ -1,7 +1,8 @@ import * as d3 from 'd3'; import rbush from 'rbush'; -import { Extent } from '../geo/index'; -import { qsString } from '../util/index'; +import { geoExtent } from '../geo/index'; +import { utilQsString } from '../util/index'; + var endpoint, cache; @@ -12,18 +13,22 @@ export function init() { } } + export function reset() { cache = rbush(); } + export function countryCode(location, callback) { - var countryCodes = cache.search({ minX: location[0], minY: location[1], maxX: location[0], maxY: location[1] }); + var countryCodes = cache.search({ + minX: location[0], minY: location[1], maxX: location[0], maxY: location[1] + }); if (countryCodes.length > 0) return callback(null, countryCodes[0].data); d3.json(endpoint + - qsString({ + utilQsString({ format: 'json', addressdetails: 1, lat: location[1], @@ -34,7 +39,7 @@ export function countryCode(location, callback) { else if (result && result.error) return callback(result.error); - var extent = Extent(location).padByMeters(1000); + var extent = geoExtent(location).padByMeters(1000); cache.insert(Object.assign(extent.bbox(), { data: result.address.country_code })); diff --git a/modules/services/taginfo.js b/modules/services/taginfo.js index 0133bb817..72d7f0de4 100644 --- a/modules/services/taginfo.js +++ b/modules/services/taginfo.js @@ -1,6 +1,7 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { qsString } from '../util/index'; +import { utilQsString } from '../util/index'; + var taginfo = {}, endpoint = 'https://taginfo.openstreetmap.org/api/4/', @@ -31,6 +32,7 @@ var taginfo = {}, relation: 'count_relation_members_fraction' }; + function sets(parameters, n, o) { if (parameters.geometry && o[parameters.geometry]) { parameters[n] = o[parameters.geometry]; @@ -38,22 +40,27 @@ function sets(parameters, n, o) { return parameters; } + function setFilter(parameters) { return sets(parameters, 'filter', tag_filters); } + function setSort(parameters) { return sets(parameters, 'sortname', tag_sorts); } + function setSortMembers(parameters) { return sets(parameters, 'sortname', tag_sort_members); } + function clean(parameters) { return _.omit(parameters, 'geometry', 'debounce'); } + function filterKeys(type) { var count_type = type ? 'count_' + type : 'count_all'; return function(d) { @@ -61,12 +68,14 @@ function filterKeys(type) { }; } + function filterMultikeys() { return function(d) { return (d.key.match(/:/g) || []).length === 1; // exactly one ':' }; } + function filterValues(allowUpperCase) { return function(d) { if (d.value.match(/[;,]/) !== null) return false; // exclude some punctuation @@ -75,6 +84,7 @@ function filterValues(allowUpperCase) { }; } + function filterRoles(geometry) { return function(d) { if (d.role === '') return false; // exclude empty role @@ -83,6 +93,7 @@ function filterRoles(geometry) { }; } + function valKey(d) { return { value: d.key, @@ -90,6 +101,7 @@ function valKey(d) { }; } + function valKeyDescription(d) { return { value: d.value, @@ -97,6 +109,7 @@ function valKeyDescription(d) { }; } + function roleKey(d) { return { value: d.role, @@ -104,6 +117,7 @@ function roleKey(d) { }; } + // sort keys with ':' lower than keys without ':' function sortKeys(a, b) { return (a.key.indexOf(':') === -1 && b.key.indexOf(':') !== -1) ? -1 @@ -111,8 +125,10 @@ function sortKeys(a, b) { : 0; } + var debounced = _.debounce(d3.json, 100, true); + function request(url, debounce, callback) { var cache = taginfo.cache; @@ -130,12 +146,13 @@ function request(url, debounce, callback) { } } + export function init() { taginfo.keys = function(parameters, callback) { var debounce = parameters.debounce; parameters = clean(setSort(parameters)); request(endpoint + 'keys/all?' + - qsString(_.extend({ + utilQsString(_.extend({ rp: 10, sortname: 'count_all', sortorder: 'desc', @@ -151,7 +168,7 @@ export function init() { var debounce = parameters.debounce; parameters = clean(setSort(parameters)); request(endpoint + 'keys/all?' + - qsString(_.extend({ + utilQsString(_.extend({ rp: 25, sortname: 'count_all', sortorder: 'desc', @@ -167,7 +184,7 @@ export function init() { var debounce = parameters.debounce; parameters = clean(setSort(setFilter(parameters))); request(endpoint + 'key/values?' + - qsString(_.extend({ + utilQsString(_.extend({ rp: 25, sortname: 'count_all', sortorder: 'desc', @@ -184,7 +201,7 @@ export function init() { var geometry = parameters.geometry; parameters = clean(setSortMembers(parameters)); request(endpoint + 'relation/roles?' + - qsString(_.extend({ + utilQsString(_.extend({ rp: 25, sortname: 'count_all_members', sortorder: 'desc', @@ -204,7 +221,7 @@ export function init() { if (parameters.value) path = 'tag/wiki_pages?'; else if (parameters.rtype) path = 'relation/wiki_pages?'; - request(endpoint + path + qsString(parameters), debounce, function(err, d) { + request(endpoint + path + utilQsString(parameters), debounce, function(err, d) { if (err) return callback(err); callback(null, d.data); }); diff --git a/modules/services/wikidata.js b/modules/services/wikidata.js index 08932a230..a1dfbe9cf 100644 --- a/modules/services/wikidata.js +++ b/modules/services/wikidata.js @@ -1,5 +1,6 @@ import { jsonpRequest } from '../util/jsonp_request'; -import { qsString } from '../util/index'; +import { utilQsString } from '../util/index'; + var wikidata = {}, endpoint = 'https://www.wikidata.org/w/api.php?'; @@ -16,7 +17,7 @@ export function init() { } lang = lang || 'en'; - jsonpRequest(endpoint + qsString({ + jsonpRequest(endpoint + utilQsString({ action: 'wbgetentities', format: 'json', sites: lang.replace(/-/g, '_') + 'wiki', diff --git a/modules/services/wikipedia.js b/modules/services/wikipedia.js index 87dcb87d0..541800fb2 100644 --- a/modules/services/wikipedia.js +++ b/modules/services/wikipedia.js @@ -1,5 +1,6 @@ import { jsonpRequest } from '../util/jsonp_request'; -import { qsString } from '../util/index'; +import { utilQsString } from '../util/index'; + var wikipedia = {}, endpoint = 'https://en.wikipedia.org/w/api.php?'; @@ -14,7 +15,7 @@ export function init() { lang = lang || 'en'; jsonpRequest(endpoint.replace('en', lang) + - qsString({ + utilQsString({ action: 'query', list: 'search', srlimit: '10', @@ -42,7 +43,7 @@ export function init() { lang = lang || 'en'; jsonpRequest(endpoint.replace('en', lang) + - qsString({ + utilQsString({ action: 'opensearch', namespace: 0, suggest: '', @@ -67,7 +68,7 @@ export function init() { } jsonpRequest(endpoint.replace('en', lang) + - qsString({ + utilQsString({ action: 'query', prop: 'langlinks', format: 'json', diff --git a/modules/svg/areas.js b/modules/svg/areas.js index e99eff835..9d39c35ee 100644 --- a/modules/svg/areas.js +++ b/modules/svg/areas.js @@ -1,10 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { Path, TagClasses } from './index'; -import { Entity } from '../core/index'; -import { isSimpleMultipolygonOuterMember } from '../geo/index'; +import { svgPath, svgTagClasses } from './index'; +import { coreEntity } from '../core/index'; +import { geoIsSimpleMultipolygonOuterMember } from '../geo/index'; -export function Areas(projection, context) { + +export function svgAreas(projection, context) { // Patterns only work in Firefox when set directly on element. // (This is not a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=750632) var patterns = { @@ -23,6 +24,7 @@ export function Areas(projection, context) { var patternKeys = ['landuse', 'natural', 'amenity']; + function setPattern(d) { for (var i = 0; i < patternKeys.length; i++) { if (patterns.hasOwnProperty(d.tags[patternKeys[i]])) { @@ -33,8 +35,9 @@ export function Areas(projection, context) { this.style.fill = this.style.stroke = ''; } + return function drawAreas(selection, graph, entities, filter) { - var path = Path(projection, graph, true), + var path = svgPath(projection, graph, true), areas = {}, multipolygon; @@ -42,7 +45,7 @@ export function Areas(projection, context) { var entity = entities[i]; if (entity.geometry(graph) !== 'area') continue; - multipolygon = isSimpleMultipolygonOuterMember(entity, graph); + multipolygon = geoIsSimpleMultipolygonOuterMember(entity, graph); if (multipolygon) { areas[multipolygon.id] = { entity: multipolygon.mergeTags(entity.tags), @@ -73,7 +76,7 @@ export function Areas(projection, context) { var clipPaths = context.surface().selectAll('defs').selectAll('.clipPath') .filter(filter) - .data(data.clip, Entity.key); + .data(data.clip, coreEntity.key); clipPaths.exit() .remove(); @@ -105,7 +108,7 @@ export function Areas(projection, context) { var paths = areagroup .selectAll('path') .filter(filter) - .data(function(layer) { return data[layer]; }, Entity.key); + .data(function(layer) { return data[layer]; }, coreEntity.key); // Remove exiting areas first, so they aren't included in the `fills` // array used for sorting below (https://github.com/openstreetmap/iD/issues/1903). @@ -137,7 +140,7 @@ export function Areas(projection, context) { setPattern.apply(this, arguments); } }) - .call(TagClasses()) + .call(svgTagClasses()) .attr('d', path); }; } diff --git a/modules/svg/debug.js b/modules/svg/debug.js index df9b135cb..1953da221 100644 --- a/modules/svg/debug.js +++ b/modules/svg/debug.js @@ -1,12 +1,13 @@ import * as d3 from 'd3'; -import { polygonIntersectsPolygon } from '../geo/index'; +import { geoPolygonIntersectsPolygon } from '../geo/index'; import { - imperial as imperialData, - driveLeft as driveLeftData, - imagery as imageryData + dataImperial as imperialData, + dataDriveLeft as driveLeftData, + dataImagery as imageryData } from '../../data/index'; -export function Debug(projection, context) { + +export function svgDebug(projection, context) { function multipolygons(imagery) { return imagery.map(function(data) { @@ -82,10 +83,10 @@ export function Debug(projection, context) { var extent = context.map().extent(), - availableImagery = showsImagery && multipolygons(imageryData.filter(function(source) { + availableImagery = showsImagery && multipolygons(dataImagery.filter(function(source) { if (!source.polygon) return false; return source.polygon.some(function(polygon) { - return polygonIntersectsPolygon(polygon, extent, true); + return geoPolygonIntersectsPolygon(polygon, extent, true); }); })); @@ -100,10 +101,9 @@ export function Debug(projection, context) { .attr('class', 'debug-imagery debug orange'); - var imperial = layer .selectAll('path.debug-imperial') - .data(showsImperial ? [imperialData] : []); + .data(showsImperial ? [dataImperial] : []); imperial.exit() .remove(); @@ -113,10 +113,9 @@ export function Debug(projection, context) { .attr('class', 'debug-imperial debug cyan'); - var driveLeft = layer .selectAll('path.debug-drive-left') - .data(showsDriveLeft ? [driveLeftData] : []); + .data(showsDriveLeft ? [dataDriveLeft] : []); driveLeft.exit() .remove(); @@ -146,5 +145,6 @@ export function Debug(projection, context) { } }; + return drawDebug; } diff --git a/modules/svg/defs.js b/modules/svg/defs.js index 8591210ee..ef9eced17 100644 --- a/modules/svg/defs.js +++ b/modules/svg/defs.js @@ -4,22 +4,23 @@ import * as d3 from 'd3'; A standalone SVG element that contains only a `defs` sub-element. To be used once globally, since defs IDs must be unique within a document. */ -export function Defs(context) { +export function svgDefs(context) { function SVGSpriteDefinition(id, href) { return function(defs) { d3.request(href) - .mimeType('image/svg+xml') - .response(function(xhr) { return xhr.responseXML; }) + .mimeType('image/svg+xml') + .response(function(xhr) { return xhr.responseXML; }) .get(function(err, svg) { if (err) return; defs.node().appendChild( - d3.select(svg.documentElement).attr('id', id).node() - ); - }); + d3.select(svg.documentElement).attr('id', id).node() + ); + }); }; } + return function drawDefs(selection) { var defs = selection.append('defs'); diff --git a/modules/svg/gpx.js b/modules/svg/gpx.js index 29c63e2a7..320fe1a93 100644 --- a/modules/svg/gpx.js +++ b/modules/svg/gpx.js @@ -1,19 +1,20 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { Extent, polygonIntersectsPolygon } from '../geo/index'; -import { Detect } from '../util/detect'; +import { geoExtent, geoPolygonIntersectsPolygon } from '../geo/index'; +import { utilDetect } from '../util/detect'; import toGeoJSON from 'togeojson'; -export function Gpx(projection, context, dispatch) { + +export function svgGpx(projection, context, dispatch) { var showLabels = true, layer; function init() { - if (Gpx.initialized) return; // run once + if (svgGpx.initialized) return; // run once - Gpx.geojson = {}; - Gpx.enabled = true; + svgGpx.geojson = {}; + svgGpx.enabled = true; function over() { d3.event.stopPropagation(); @@ -26,20 +27,20 @@ export function Gpx(projection, context, dispatch) { .on('drop.localgpx', function() { d3.event.stopPropagation(); d3.event.preventDefault(); - if (!Detect().filedrop) return; + if (!utilDetect().filedrop) return; drawGpx.files(d3.event.dataTransfer.files); }) .on('dragenter.localgpx', over) .on('dragexit.localgpx', over) .on('dragover.localgpx', over); - Gpx.initialized = true; + svgGpx.initialized = true; } function drawGpx(selection) { - var geojson = Gpx.geojson, - enabled = Gpx.enabled; + var geojson = svgGpx.geojson, + enabled = svgGpx.enabled; layer = selection.selectAll('.layer-gpx') .data(enabled ? [0] : []); @@ -170,8 +171,8 @@ export function Gpx(projection, context, dispatch) { return _.union(coords, feature.geometry.type === 'Point' ? [c] : c); }, []); - if (!polygonIntersectsPolygon(viewport, coords, true)) { - var extent = Extent(d3.geoBounds(geojson)); + if (!geoPolygonIntersectsPolygon(viewport, coords, true)) { + var extent = geoExtent(d3.geoBounds(geojson)); map.centerZoom(extent.center(), map.trimmedExtentZoom(extent)); } diff --git a/modules/svg/icon.js b/modules/svg/icon.js index 97f8a1f34..8d287ad3a 100644 --- a/modules/svg/icon.js +++ b/modules/svg/icon.js @@ -1,4 +1,4 @@ -export function Icon(name, svgklass, useklass) { +export function svgIcon(name, svgklass, useklass) { return function drawIcon(selection) { selection.selectAll('svg') .data([0]) diff --git a/modules/svg/index.js b/modules/svg/index.js index 27d16672f..9b48ba93d 100644 --- a/modules/svg/index.js +++ b/modules/svg/index.js @@ -1,20 +1,20 @@ -export { Areas } from './areas.js'; -export { Debug } from './debug.js'; -export { Defs } from './defs.js'; -export { Gpx } from './gpx.js'; -export { Icon } from './icon.js'; -export { Labels } from './labels.js'; -export { Layers } from './layers.js'; -export { Lines } from './lines.js'; -export { MapillaryImages } from './mapillary_images.js'; -export { MapillarySigns } from './mapillary_signs.js'; -export { Midpoints } from './midpoints.js'; -export { OneWaySegments } from './one_way_segments.js'; -export { Osm } from './osm.js'; -export { Path } from './path.js'; -export { PointTransform } from './point_transform.js'; -export { Points } from './points.js'; -export { RelationMemberTags } from './relation_member_tags.js'; -export { TagClasses } from './tag_classes.js'; -export { Turns } from './turns.js'; -export { Vertices } from './vertices.js'; +export { svgAreas } from './areas.js'; +export { svgDebug } from './debug.js'; +export { svgDefs } from './defs.js'; +export { svgGpx } from './gpx.js'; +export { svgIcon } from './icon.js'; +export { svgLabels } from './labels.js'; +export { svgLayers } from './layers.js'; +export { svgLines } from './lines.js'; +export { svgMapillaryImages } from './mapillary_images.js'; +export { svgMapillarySigns } from './mapillary_signs.js'; +export { svgMidpoints } from './midpoints.js'; +export { svgOneWaySegments } from './one_way_segments.js'; +export { svgOsm } from './osm.js'; +export { svgPath } from './path.js'; +export { svgPointTransform } from './point_transform.js'; +export { svgPoints } from './points.js'; +export { svgRelationMemberTags } from './relation_member_tags.js'; +export { svgTagClasses } from './tag_classes.js'; +export { svgTurns } from './turns.js'; +export { svgVertices } from './vertices.js'; diff --git a/modules/svg/labels.js b/modules/svg/labels.js index 60a592239..89d156de3 100644 --- a/modules/svg/labels.js +++ b/modules/svg/labels.js @@ -1,12 +1,12 @@ import * as d3 from 'd3'; import _ from 'lodash'; import rbush from 'rbush'; -import { displayName, getStyle } from '../util/index'; -import { Entity } from '../core/index'; -import { pathLength } from '../geo/index'; +import { utilDisplayName, utilGetStyle } from '../util/index'; +import { coreEntity } from '../core/index'; +import { geoPathLength } from '../geo/index'; -export function Labels(projection, context) { +export function svgLabels(projection, context) { var path = d3.geoPath().projection(projection); // Replace with dict and iterate over entities tags instead? @@ -41,11 +41,11 @@ export function Labels(projection, context) { var default_size = 12; var font_sizes = label_stack.map(function(d) { - var style = getStyle('text.' + d[0] + '.tag-' + d[1]), + var style = utilGetStyle('text.' + d[0] + '.tag-' + d[1]), m = style && style.cssText.match('font-size: ([0-9]{1,2})px;'); if (m) return parseInt(m[1], 10); - style = getStyle('text.' + d[0]); + style = utilGetStyle('text.' + d[0]); m = style && style.cssText.match('font-size: ([0-9]{1,2})px;'); if (m) return parseInt(m[1], 10); @@ -104,7 +104,7 @@ export function Labels(projection, context) { function drawLinePaths(selection, entities, filter, classes, labels) { var paths = selection.selectAll('path') .filter(filter) - .data(entities, Entity.key); + .data(entities, coreEntity.key); paths.exit() .remove(); @@ -122,7 +122,7 @@ export function Labels(projection, context) { function drawLineLabels(selection, entities, filter, classes, labels) { var texts = selection.selectAll('text.' + classes) .filter(filter) - .data(entities, Entity.key); + .data(entities, coreEntity.key); texts.exit() .remove(); @@ -137,17 +137,17 @@ export function Labels(projection, context) { texts.selectAll('.textpath') .filter(filter) - .data(entities, Entity.key) + .data(entities, coreEntity.key) .attr('startOffset', '50%') .attr('xlink:href', function(d) { return '#labelpath-' + d.id; }) - .text(displayName); + .text(utilDisplayName); } function drawPointLabels(selection, entities, filter, classes, labels) { var texts = selection.selectAll('text.' + classes) .filter(filter) - .data(entities, Entity.key); + .data(entities, coreEntity.key); texts.exit() .remove(); @@ -163,9 +163,9 @@ export function Labels(projection, context) { .attr('x', get(labels, 'x')) .attr('y', get(labels, 'y')) .style('text-anchor', get(labels, 'textAnchor')) - .text(displayName) + .text(utilDisplayName) .each(function(d, i) { - textWidth(displayName(d), labels[i].height, this); + textWidth(utilDisplayName(d), labels[i].height, this); }); } @@ -184,7 +184,7 @@ export function Labels(projection, context) { function drawAreaIcons(selection, entities, filter, classes, labels) { var icons = selection.selectAll('use') .filter(filter) - .data(entities, Entity.key); + .data(entities, coreEntity.key); icons.exit() .remove(); @@ -303,7 +303,7 @@ export function Labels(projection, context) { var preset = geometry === 'area' && context.presets().match(entity, graph), icon = preset && !blacklisted(preset) && preset.icon; - if (!icon && !displayName(entity)) + if (!icon && !utilDisplayName(entity)) continue; for (k = 0; k < label_stack.length; k++) { @@ -331,7 +331,7 @@ export function Labels(projection, context) { var font_size = font_sizes[k]; for (i = 0; i < labelable[k].length; i++) { entity = labelable[k][i]; - var name = displayName(entity), + var name = utilDisplayName(entity), width = name && textWidth(name, font_size), p; if (entity.geometry(graph) === 'point') { @@ -368,7 +368,7 @@ export function Labels(projection, context) { function getLineLabel(entity, width, height) { var nodes = _.map(graph.childNodes(entity), 'loc').map(projection), - length = pathLength(nodes); + length = geoPathLength(nodes); if (length < width + 20) return; for (var i = 0; i < lineOffsets.length; i++) { diff --git a/modules/svg/layers.js b/modules/svg/layers.js index 2bd5fd58f..98b5ba901 100644 --- a/modules/svg/layers.js +++ b/modules/svg/layers.js @@ -1,23 +1,23 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; -import { getDimensions, setDimensions } from '../util/dimensions'; -import { Debug } from './debug'; -import { Gpx } from './gpx'; -import { MapillaryImages } from './mapillary_images'; -import { MapillarySigns } from './mapillary_signs'; -import { Osm } from './osm'; +import { utilRebind } from '../util/rebind'; +import { utilGetDimensions, utilSetDimensions } from '../util/dimensions'; +import { svgDebug } from './debug'; +import { svgGpx } from './gpx'; +import { svgMapillaryImages } from './mapillary_images'; +import { svgMapillarySigns } from './mapillary_signs'; +import { svgOsm } from './osm'; -export function Layers(projection, context) { +export function svgLayers(projection, context) { var dispatch = d3.dispatch('change'), svg = d3.select(null), layers = [ - { id: 'osm', layer: Osm(projection, context, dispatch) }, - { id: 'gpx', layer: Gpx(projection, context, dispatch) }, - { id: 'mapillary-images', layer: MapillaryImages(projection, context, dispatch) }, - { id: 'mapillary-signs', layer: MapillarySigns(projection, context, dispatch) }, - { id: 'debug', layer: Debug(projection, context, dispatch) } + { id: 'osm', layer: svgOsm(projection, context, dispatch) }, + { id: 'gpx', layer: svgGpx(projection, context, dispatch) }, + { id: 'mapillary-images', layer: svgMapillaryImages(projection, context, dispatch) }, + { id: 'mapillary-signs', layer: svgMapillarySigns(projection, context, dispatch) }, + { id: 'debug', layer: svgDebug(projection, context, dispatch) } ]; @@ -92,8 +92,8 @@ export function Layers(projection, context) { drawLayers.dimensions = function(_) { - if (!arguments.length) return getDimensions(svg); - setDimensions(svg, _); + if (!arguments.length) return utilGetDimensions(svg); + utilSetDimensions(svg, _); layers.forEach(function(obj) { if (obj.layer.dimensions) { obj.layer.dimensions(_); @@ -103,5 +103,5 @@ export function Layers(projection, context) { }; - return rebind(drawLayers, dispatch, 'on'); + return utilRebind(drawLayers, dispatch, 'on'); } diff --git a/modules/svg/lines.js b/modules/svg/lines.js index 76f3511c0..dcf4b2da3 100644 --- a/modules/svg/lines.js +++ b/modules/svg/lines.js @@ -1,11 +1,18 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { OneWaySegments, Path, RelationMemberTags, TagClasses } from './index'; -import { Detect } from '../util/detect'; -import { Entity } from '../core/index'; -import { simpleMultipolygonOuterMember } from '../geo/index'; +import { + svgOneWaySegments, + svgPath, + svgRelationMemberTags, + svgTagClasses +} from './index'; -export function Lines(projection) { +import { utilDetect } from '../util/detect'; +import { coreEntity } from '../core/index'; +import { geoSimpleMultipolygonOuterMember } from '../geo/index'; + + +export function svgLines(projection) { var highway_stack = { motorway: 0, @@ -24,19 +31,19 @@ export function Lines(projection) { function waystack(a, b) { var as = 0, bs = 0; - if (a.tags.highway) { as -= highway_stack[a.tags.highway]; } if (b.tags.highway) { bs -= highway_stack[b.tags.highway]; } return as - bs; } + return function drawLines(selection, graph, entities, filter) { var ways = [], pathdata = {}, onewaydata = {}, - getPath = Path(projection, graph); + getPath = svgPath(projection, graph); for (var i = 0; i < entities.length; i++) { var entity = entities[i], - outer = simpleMultipolygonOuterMember(entity, graph); + outer = geoSimpleMultipolygonOuterMember(entity, graph); if (outer) { ways.push(entity.mergeTags(outer.tags)); } else if (entity.geometry(graph) === 'line') { @@ -83,22 +90,22 @@ export function Lines(projection) { .filter(filter) .data( function() { return pathdata[this.parentNode.__data__] || []; }, - Entity.key + coreEntity.key ); lines.exit() .remove(); // Optimization: call simple TagClasses only on enter selection. This - // works because Entity.key is defined to include the entity v attribute. + // works because coreEntity.key is defined to include the entity v attribute. lines.enter() .append('path') .attr('class', function(d) { return 'way line ' + this.parentNode.__data__ + ' ' + d.id; }) - .call(TagClasses()) + .call(svgTagClasses()) .merge(lines) .sort(waystack) .attr('d', getPath) - .call(TagClasses().tags(RelationMemberTags(graph))); + .call(svgTagClasses().tags(svgRelationMemberTags(graph))); var onewaygroup = layergroup @@ -129,7 +136,7 @@ export function Lines(projection) { .merge(oneways) .attr('d', function(d) { return d.d; }); - if (Detect().ie) { + if (utilDetect().ie) { oneways.each(function() { this.parentNode.insertBefore(this, this); }); } }; diff --git a/modules/svg/mapillary_images.js b/modules/svg/mapillary_images.js index c9fbf713a..7e716186c 100644 --- a/modules/svg/mapillary_images.js +++ b/modules/svg/mapillary_images.js @@ -1,10 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { PointTransform } from './point_transform'; -import { getDimensions, setDimensions } from '../util/dimensions'; -import { mapillary as mapillaryService } from '../services/index'; +import { svgPointTransform } from './point_transform'; +import { utilGetDimensions, utilSetDimensions } from '../util/dimensions'; +import { serviceMapillary } from '../services/index'; -export function MapillaryImages(projection, context, dispatch) { + +export function svgMapillaryImages(projection, context, dispatch) { var debouncedRedraw = _.debounce(function () { dispatch.call('change'); }, 1000), minZoom = 12, layer = d3.select(null), @@ -12,17 +13,17 @@ export function MapillaryImages(projection, context, dispatch) { function init() { - if (MapillaryImages.initialized) return; // run once - MapillaryImages.enabled = false; - MapillaryImages.initialized = true; + if (svgMapillaryImages.initialized) return; // run once + svgMapillaryImages.enabled = false; + svgMapillaryImages.initialized = true; } function getMapillary() { - if (mapillaryService && !_mapillary) { - _mapillary = mapillaryService.init(); + if (serviceMapillary && !_mapillary) { + _mapillary = serviceMapillary.init(); _mapillary.event.on('loadedImages', debouncedRedraw); - } else if (!mapillaryService && _mapillary) { + } else if (!serviceMapillary && _mapillary) { _mapillary = null; } @@ -87,7 +88,7 @@ export function MapillaryImages(projection, context, dispatch) { function transform(d) { - var t = PointTransform(projection)(d); + var t = svgPointTransform(projection)(d); if (d.ca) t += ' rotate(' + Math.floor(d.ca) + ',0,0)'; return t; } @@ -95,7 +96,7 @@ export function MapillaryImages(projection, context, dispatch) { function update() { var mapillary = getMapillary(), - data = (mapillary ? mapillary.images(projection, getDimensions(layer)) : []), + data = (mapillary ? mapillary.images(projection, utilGetDimensions(layer)) : []), imageKey = mapillary ? mapillary.getSelectedImage() : null; var markers = layer.selectAll('.viewfield-group') @@ -127,7 +128,7 @@ export function MapillaryImages(projection, context, dispatch) { function drawImages(selection) { - var enabled = MapillaryImages.enabled, + var enabled = svgMapillaryImages.enabled, mapillary = getMapillary(); layer = selection.selectAll('.layer-mapillary-images') @@ -146,7 +147,7 @@ export function MapillaryImages(projection, context, dispatch) { if (mapillary && ~~context.map().zoom() >= minZoom) { editOn(); update(); - mapillary.loadImages(projection, getDimensions(layer)); + mapillary.loadImages(projection, utilGetDimensions(layer)); } else { editOff(); } @@ -155,9 +156,9 @@ export function MapillaryImages(projection, context, dispatch) { drawImages.enabled = function(_) { - if (!arguments.length) return MapillaryImages.enabled; - MapillaryImages.enabled = _; - if (MapillaryImages.enabled) { + if (!arguments.length) return svgMapillaryImages.enabled; + svgMapillaryImages.enabled = _; + if (svgMapillaryImages.enabled) { showLayer(); } else { hideLayer(); @@ -173,8 +174,8 @@ export function MapillaryImages(projection, context, dispatch) { drawImages.dimensions = function(_) { - if (!arguments.length) return getDimensions(layer); - setDimensions(layer, _); + if (!arguments.length) return utilGetDimensions(layer); + utilSetDimensions(layer, _); return this; }; diff --git a/modules/svg/mapillary_signs.js b/modules/svg/mapillary_signs.js index a0e9e4902..9aa8b6f52 100644 --- a/modules/svg/mapillary_signs.js +++ b/modules/svg/mapillary_signs.js @@ -1,11 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { getDimensions, setDimensions } from '../util/dimensions'; -import { PointTransform } from './point_transform'; -import { mapillary as mapillaryService } from '../services/index'; +import { utilGetDimensions, utilSetDimensions } from '../util/dimensions'; +import { svgPointTransform } from './point_transform'; +import { serviceMapillary } from '../services/index'; -export function MapillarySigns(projection, context, dispatch) { +export function svgMapillarySigns(projection, context, dispatch) { var debouncedRedraw = _.debounce(function () { dispatch.call('change'); }, 1000), minZoom = 12, layer = d3.select(null), @@ -13,17 +13,17 @@ export function MapillarySigns(projection, context, dispatch) { function init() { - if (MapillarySigns.initialized) return; // run once - MapillarySigns.enabled = false; - MapillarySigns.initialized = true; + if (svgMapillarySigns.initialized) return; // run once + svgMapillarySigns.enabled = false; + svgMapillarySigns.initialized = true; } function getMapillary() { - if (mapillaryService && !_mapillary) { - _mapillary = mapillaryService.init(); + if (serviceMapillary && !_mapillary) { + _mapillary = serviceMapillary.init(); _mapillary.event.on('loadedSigns', debouncedRedraw); - } else if (!mapillaryService && _mapillary) { + } else if (!serviceMapillary && _mapillary) { _mapillary = null; } return _mapillary; @@ -68,7 +68,7 @@ export function MapillarySigns(projection, context, dispatch) { function update() { var mapillary = getMapillary(), - data = (mapillary ? mapillary.signs(projection, getDimensions(layer)) : []), + data = (mapillary ? mapillary.signs(projection, utilGetDimensions(layer)) : []), imageKey = mapillary ? mapillary.getSelectedImage() : null; var signs = layer.selectAll('.icon-sign') @@ -91,12 +91,12 @@ export function MapillarySigns(projection, context, dispatch) { signs .merge(enter) - .attr('transform', PointTransform(projection)); + .attr('transform', svgPointTransform(projection)); } function drawSigns(selection) { - var enabled = MapillarySigns.enabled, + var enabled = svgMapillarySigns.enabled, mapillary = getMapillary(); layer = selection.selectAll('.layer-mapillary-signs') @@ -116,7 +116,7 @@ export function MapillarySigns(projection, context, dispatch) { if (mapillary && ~~context.map().zoom() >= minZoom) { editOn(); update(); - mapillary.loadSigns(context, projection, getDimensions(layer)); + mapillary.loadSigns(context, projection, utilGetDimensions(layer)); } else { editOff(); } @@ -125,9 +125,9 @@ export function MapillarySigns(projection, context, dispatch) { drawSigns.enabled = function(_) { - if (!arguments.length) return MapillarySigns.enabled; - MapillarySigns.enabled = _; - if (MapillarySigns.enabled) { + if (!arguments.length) return svgMapillarySigns.enabled; + svgMapillarySigns.enabled = _; + if (svgMapillarySigns.enabled) { showLayer(); } else { hideLayer(); @@ -144,8 +144,8 @@ export function MapillarySigns(projection, context, dispatch) { drawSigns.dimensions = function(_) { - if (!arguments.length) return getDimensions(layer); - setDimensions(layer, _); + if (!arguments.length) return utilGetDimensions(layer); + utilSetDimensions(layer, _); return this; }; diff --git a/modules/svg/midpoints.js b/modules/svg/midpoints.js index f134d15c7..2c88078e9 100644 --- a/modules/svg/midpoints.js +++ b/modules/svg/midpoints.js @@ -1,8 +1,19 @@ import _ from 'lodash'; -import { PointTransform, TagClasses } from './index'; -import { angle, euclideanDistance, interp, lineIntersection } from '../geo/index'; +import { + svgPointTransform, + svgTagClasses +} from './index'; + +import { + geoAngle, + geoEuclideanDistance, + geoInterp, + geoLineIntersection +} from '../geo/index'; + + +export function svgMidpoints(projection, context) { -export function Midpoints(projection, context) { return function drawMidpoints(selection, graph, entities, filter, extent) { var poly = extent.polygon(), midpoints = {}; @@ -27,18 +38,18 @@ export function Midpoints(projection, context) { if (midpoints[id]) { midpoints[id].parents.push(entity); } else { - if (euclideanDistance(projection(a.loc), projection(b.loc)) > 40) { - var point = interp(a.loc, b.loc, 0.5), + if (geoEuclideanDistance(projection(a.loc), projection(b.loc)) > 40) { + var point = geoInterp(a.loc, b.loc, 0.5), loc = null; if (extent.intersects(point)) { loc = point; } else { for (var k = 0; k < 4; k++) { - point = lineIntersection([a.loc, b.loc], [poly[k], poly[k+1]]); + point = geoLineIntersection([a.loc, b.loc], [poly[k], poly[k + 1]]); if (point && - euclideanDistance(projection(a.loc), projection(point)) > 20 && - euclideanDistance(projection(b.loc), projection(point)) > 20) + geoEuclideanDistance(projection(a.loc), projection(point)) > 20 && + geoEuclideanDistance(projection(b.loc), projection(point)) > 20) { loc = point; break; @@ -60,6 +71,7 @@ export function Midpoints(projection, context) { } } + function midpointFilter(d) { if (midpoints[d.id]) return true; @@ -102,10 +114,10 @@ export function Midpoints(projection, context) { var translate = PointTransform(projection), a = graph.entity(d.edge[0]), b = graph.entity(d.edge[1]), - angleVal = Math.round(angle(a, b, projection) * (180 / Math.PI)); + angleVal = Math.round(geoAngle(a, b, projection) * (180 / Math.PI)); return translate(d) + ' rotate(' + angleVal + ')'; }) - .call(TagClasses().tags( + .call(svgTagClasses().tags( function(d) { return d.parents[0].tags; } )); diff --git a/modules/svg/one_way_segments.js b/modules/svg/one_way_segments.js index 079a17972..e1568a620 100644 --- a/modules/svg/one_way_segments.js +++ b/modules/svg/one_way_segments.js @@ -1,7 +1,8 @@ import * as d3 from 'd3'; -import { euclideanDistance } from '../geo/index'; +import { geoEuclideanDistance } from '../geo/index'; -export function OneWaySegments(projection, graph, dt) { + +export function svgOneWaySegments(projection, graph, dt) { return function(entity) { var a, b, @@ -27,7 +28,7 @@ export function OneWaySegments(projection, graph, dt) { b = [x, y]; if (a) { - var span = euclideanDistance(a, b) - offset; + var span = geoEuclideanDistance(a, b) - offset; if (span >= 0) { var angle = Math.atan2(b[1] - a[1], b[0] - a[0]), diff --git a/modules/svg/osm.js b/modules/svg/osm.js index 8f5706cc0..4576ad9b5 100644 --- a/modules/svg/osm.js +++ b/modules/svg/osm.js @@ -1,4 +1,4 @@ -export function Osm() { +export function svgOsm() { return function drawOsm(selection) { var layers = selection.selectAll('.layer-osm') .data(['areas', 'lines', 'hit', 'halo', 'label']); diff --git a/modules/svg/path.js b/modules/svg/path.js index 0819b3bc8..e6091ed9e 100644 --- a/modules/svg/path.js +++ b/modules/svg/path.js @@ -1,6 +1,6 @@ import * as d3 from 'd3'; -export function Path(projection, graph, polygon) { +export function svgPath(projection, graph, polygon) { var cache = {}, clip = d3.geoClipExtent().extent(projection.clipExtent()).stream, project = projection.stream, diff --git a/modules/svg/point_transform.js b/modules/svg/point_transform.js index 313d82009..cf80f845d 100644 --- a/modules/svg/point_transform.js +++ b/modules/svg/point_transform.js @@ -1,4 +1,4 @@ -export function PointTransform(projection) { +export function svgPointTransform(projection) { return function(entity) { // http://jsperf.com/short-array-join var pt = projection(entity.loc); diff --git a/modules/svg/points.js b/modules/svg/points.js index 812acfc3f..150f8d609 100644 --- a/modules/svg/points.js +++ b/modules/svg/points.js @@ -1,9 +1,10 @@ import _ from 'lodash'; -import { PointTransform, TagClasses } from './index'; -import { Entity } from '../core/index'; +import { svgPointTransform, svgTagClasses } from './index'; +import { coreEntity } from '../core/index'; -export function Points(projection, context) { +export function svgPoints(projection, context) { + function markerPath(selection, klass) { selection .attr('class', klass) @@ -15,6 +16,7 @@ export function Points(projection, context) { return b.loc[1] - a.loc[1]; } + return function drawPoints(selection, graph, entities, filter) { var wireframe = context.surface().classed('fill-wireframe'), points = wireframe ? [] : _.filter(entities, function(e) { @@ -27,7 +29,7 @@ export function Points(projection, context) { var groups = layer.selectAll('g.point') .filter(filter) - .data(points, Entity.key); + .data(points, coreEntity.key); groups.exit() .remove(); @@ -51,8 +53,8 @@ export function Points(projection, context) { groups = groups .merge(enter) - .attr('transform', PointTransform(projection)) - .call(TagClasses()); + .attr('transform', svgPointTransform(projection)) + .call(svgTagClasses()); // Selecting the following implicitly // sets the data (point entity) on the element diff --git a/modules/svg/relation_member_tags.js b/modules/svg/relation_member_tags.js index 94fa6c567..7a4661eaf 100644 --- a/modules/svg/relation_member_tags.js +++ b/modules/svg/relation_member_tags.js @@ -1,6 +1,6 @@ import _ from 'lodash'; -export function RelationMemberTags(graph) { +export function svgRelationMemberTags(graph) { return function(entity) { var tags = entity.tags; graph.parentRelations(entity).forEach(function(relation) { diff --git a/modules/svg/tag_classes.js b/modules/svg/tag_classes.js index c311f1b0b..ab501a5e7 100644 --- a/modules/svg/tag_classes.js +++ b/modules/svg/tag_classes.js @@ -1,7 +1,8 @@ import * as d3 from 'd3'; -import { pavedTags } from '../core/tags'; +import { corePavedTags } from '../core/tags'; -export function TagClasses() { + +export function svgTagClasses() { var primaries = [ 'building', 'highway', 'railway', 'waterway', 'aeroway', 'motorway', 'boundary', 'power', 'amenity', 'natural', 'landuse', @@ -88,8 +89,8 @@ export function TagClasses() { var paved = (t.highway !== 'track'); for (k in t) { v = t[k]; - if (k in pavedTags) { - paved = !!pavedTags[k][v]; + if (k in corePavedTags) { + paved = !!corePavedTags[k][v]; break; } } @@ -106,6 +107,7 @@ export function TagClasses() { }); }; + tagClasses.tags = function(_) { if (!arguments.length) return tags; tags = _; diff --git a/modules/svg/turns.js b/modules/svg/turns.js index 48d22d2d5..e2cd4d39b 100644 --- a/modules/svg/turns.js +++ b/modules/svg/turns.js @@ -1,8 +1,10 @@ -import { angle } from '../geo/index'; +import { geoAngle } from '../geo/index'; -export function Turns(projection) { +export function svgTurns(projection) { + return function drawTurns(selection, graph, turns) { + function key(turn) { return [turn.from.node + turn.via.node + turn.to.node].join('-'); } @@ -58,7 +60,7 @@ export function Turns(projection) { .attr('transform', function (turn) { var v = graph.entity(turn.via.node), t = graph.entity(turn.to.node), - a = angle(v, t, projection), + a = geoAngle(v, t, projection), p = projection(v.loc), r = turn.u ? 0 : 60; diff --git a/modules/svg/vertices.js b/modules/svg/vertices.js index 2e1649fa9..9038446fd 100644 --- a/modules/svg/vertices.js +++ b/modules/svg/vertices.js @@ -1,8 +1,9 @@ import * as d3 from 'd3'; -import { Entity } from '../core/index'; -import { PointTransform } from './index'; +import { coreEntity } from '../core/index'; +import { svgPointTransform } from './index'; -export function Vertices(projection, context) { + +export function svgVertices(projection, context) { var radiuses = { // z16-, z17, z18+, tagged shadow: [6, 7.5, 7.5, 11.5], @@ -101,7 +102,7 @@ export function Vertices(projection, context) { z = (zoom < 17 ? 0 : zoom < 18 ? 1 : 2); var groups = selection - .data(vertices, Entity.key); + .data(vertices, coreEntity.key); groups.exit() .remove(); @@ -132,7 +133,7 @@ export function Vertices(projection, context) { groups .merge(enter) - .attr('transform', PointTransform(projection)) + .attr('transform', svgPointTransform(projection)) .classed('shared', function(entity) { return graph.isShared(entity); }) .call(setAttributes); } diff --git a/modules/ui/account.js b/modules/ui/account.js index f92e559d4..3f1b41f58 100644 --- a/modules/ui/account.js +++ b/modules/ui/account.js @@ -1,10 +1,12 @@ import * as d3 from 'd3'; import { t } from '../util/locale'; -import { Icon } from '../svg/index'; +import { svgIcon } from '../svg/index'; -export function Account(context) { + +export function uiAccount(context) { var connection = context.connection(); + function update(selection) { if (!connection.authenticated()) { selection.selectAll('#userLink, #logoutLink') @@ -36,7 +38,7 @@ export function Account(context) { .attr('src', details.image_url); } else { userLink - .call(Icon('#icon-avatar', 'pre-text light')); + .call(svgIcon('#icon-avatar', 'pre-text light')); } // Add user name @@ -55,6 +57,7 @@ export function Account(context) { }); } + return function(selection) { selection.append('li') .attr('id', 'logoutLink') diff --git a/modules/ui/attribution.js b/modules/ui/attribution.js index 5d5bc5346..7f445e40f 100644 --- a/modules/ui/attribution.js +++ b/modules/ui/attribution.js @@ -2,9 +2,10 @@ import * as d3 from 'd3'; import _ from 'lodash'; -export function Attribution(context) { +export function uiAttribution(context) { var selection; + function attribution(data, klass) { var div = selection.selectAll('.' + klass) .data([0]); diff --git a/modules/ui/background.js b/modules/ui/background.js index 7826c1542..5334ea955 100644 --- a/modules/ui/background.js +++ b/modules/ui/background.js @@ -2,17 +2,18 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; +import { rendererBackgroundSource } from '../renderer/index'; +import { geoMetersToOffset, geoOffsetToMeters } from '../geo/index'; +import { utilDetect } from '../util/detect'; +import { utilSetTransform } from '../util/index'; +import { svgIcon } from '../svg/index'; +import { uiMapInMap } from './map_in_map'; +import { uiCmd } from './cmd'; +import { uiTooltipHtml } from './tooltipHtml'; import { tooltip } from '../util/tooltip'; -import { metersToOffset, offsetToMeters } from '../geo/index'; -import { BackgroundSource } from '../renderer/index'; -import { Detect } from '../util/detect'; -import { Icon } from '../svg/index'; -import { MapInMap } from './map_in_map'; -import { cmd } from './cmd'; -import { setTransform } from '../util/index'; -import { tooltipHtml } from './tooltipHtml'; -export function Background(context) { + +export function uiBackground(context) { var key = 'B', opacities = [1, 0.75, 0.5, 0.25], directions = [ @@ -37,14 +38,15 @@ export function Background(context) { : d3.descending(a.area(), b.area()) || d3.ascending(a.name(), b.name()) || 0; } + function setOpacity(d) { var bg = context.container().selectAll('.layer-background') .transition() .style('opacity', d) .attr('data-opacity', d); - if (!Detect().opera) { - setTransform(bg, 0, 0); + if (!utilDetect().opera) { + utilSetTransform(bg, 0, 0); } opacityList.selectAll('li') @@ -53,6 +55,7 @@ export function Background(context) { context.storage('background-opacity', d); } + function setTooltips(selection) { selection.each(function(d) { var item = d3.select(this); @@ -61,7 +64,7 @@ export function Background(context) { .html(true) .title(function() { var tip = '
' + t('background.switch') + '
'; - return tooltipHtml(tip, cmd('⌘B')); + return uiTooltipHtml(tip, uiCmd('⌘B')); }) .placement('top') ); @@ -76,6 +79,7 @@ export function Background(context) { }); } + function selectLayer() { function active(d) { return context.background().showsLayer(d); @@ -89,6 +93,7 @@ export function Background(context) { .property('checked', active); } + function clickSetSource(d) { previous = context.background().baseLayerSource(); d3.event.preventDefault(); @@ -97,6 +102,7 @@ export function Background(context) { document.activeElement.blur(); } + function editCustom() { d3.event.preventDefault(); var template = window.prompt(t('background.custom_prompt'), customTemplate); @@ -110,12 +116,14 @@ export function Background(context) { setCustom(template); } + function setCustom(template) { - context.background().baseLayerSource(BackgroundSource.Custom(template)); + context.background().baseLayerSource(rendererBackgroundSource.Custom(template)); selectLayer(); context.storage('background-custom-template', template); } + function clickSetOverlay(d) { d3.event.preventDefault(); context.background().toggleOverlayLayer(d); @@ -123,6 +131,7 @@ export function Background(context) { document.activeElement.blur(); } + function drawList(layerList, type, change, filter) { var sources = context.background() .sources(context.map().extent()) @@ -164,6 +173,7 @@ export function Background(context) { .style('display', layerList.selectAll('li.layer').data().length > 0 ? 'block' : 'none'); } + function update() { backgroundList.call(drawList, 'radio', clickSetSource, function(d) { return !d.overlay; }); overlayList.call(drawList, 'checkbox', clickSetOverlay, function(d) { return d.overlay; }); @@ -178,8 +188,9 @@ export function Background(context) { updateOffsetVal(); } + function updateOffsetVal() { - var meters = offsetToMeters(context.background().offset()), + var meters = geoOffsetToMeters(context.background().offset()), x = +meters[0].toFixed(2), y = +meters[1].toFixed(2); @@ -194,16 +205,19 @@ export function Background(context) { }); } + function resetOffset() { context.background().offset([0, 0]); updateOffsetVal(); } + function nudge(d) { context.background().nudge(d, context.map().zoom()); updateOffsetVal(); } + function buttonOffset(d) { var timeout = window.setTimeout(function() { interval = window.setInterval(nudge.bind(null, d), 100); @@ -219,6 +233,7 @@ export function Background(context) { nudge(d); } + function inputOffset() { var input = d3.select(this); var d = input.node().value; @@ -235,10 +250,11 @@ export function Background(context) { return; } - context.background().offset(metersToOffset(d)); + context.background().offset(geoMetersToOffset(d)); updateOffsetVal(); } + function dragOffset() { var origin = [d3.event.clientX, d3.event.clientY]; @@ -269,22 +285,26 @@ export function Background(context) { d3.event.preventDefault(); } + function hide() { setVisible(false); } + function toggle() { if (d3.event) d3.event.preventDefault(); tooltipBehavior.hide(button); setVisible(!button.classed('active')); } + function quickSwitch() { if (previous) { clickSetSource(previous); } } + function setVisible(show) { if (show !== shown) { button.classed('active', show); @@ -319,11 +339,11 @@ export function Background(context) { tooltipBehavior = tooltip() .placement('left') .html(true) - .title(tooltipHtml(t('background.description'), key)), + .title(uiTooltipHtml(t('background.description'), key)), button = selection.append('button') .attr('tabindex', -1) .on('click', toggle) - .call(Icon('#icon-layers', 'light')) + .call(svgIcon('#icon-layers', 'light')) .call(tooltipBehavior), shown = false; @@ -362,7 +382,7 @@ export function Background(context) { var custom = backgroundList.append('li') .attr('class', 'custom_layer') - .datum(BackgroundSource.Custom()); + .datum(rendererBackgroundSource.Custom()); custom.append('button') .attr('class', 'layer-browse') @@ -370,7 +390,7 @@ export function Background(context) { .title(t('background.custom_button')) .placement('left')) .on('click', editCustom) - .call(Icon('#icon-search')); + .call(svgIcon('#icon-search')); var label = custom.append('label'); @@ -393,7 +413,7 @@ export function Background(context) { .append('a') .attr('target', '_blank') .attr('tabindex', -1) - .call(Icon('#icon-out-link', 'inline')) + .call(svgIcon('#icon-out-link', 'inline')) .attr('href', 'https://github.com/openstreetmap/iD/blob/master/FAQ.md#how-can-i-report-an-issue-with-background-imagery') .append('span') .text(t('background.imagery_source_faq')); @@ -411,7 +431,7 @@ export function Background(context) { .append('label') .call(tooltip() .html(true) - .title(tooltipHtml(t('background.minimap.tooltip'), '/')) + .title(uiTooltipHtml(t('background.minimap.tooltip'), '/')) .placement('top') ); @@ -419,7 +439,7 @@ export function Background(context) { .append('input') .attr('type', 'checkbox') .on('change', function() { - MapInMap.toggle(); + uiMapInMap.toggle(); d3.event.preventDefault(); }); @@ -477,7 +497,7 @@ export function Background(context) { .attr('title', t('background.reset')) .attr('class', 'nudge-reset disabled') .on('click', resetOffset) - .call(Icon('#icon-undo')); + .call(svgIcon('#icon-undo')); context.map() .on('move.background-update', _.debounce(update, 1000)); @@ -491,7 +511,7 @@ export function Background(context) { var keybinding = d3keybinding('background') .on(key, toggle) - .on(cmd('⌘B'), quickSwitch) + .on(uiCmd('⌘B'), quickSwitch) .on('F', hide) .on('H', hide); diff --git a/modules/ui/cmd.js b/modules/ui/cmd.js index 21e717633..ba09c232e 100644 --- a/modules/ui/cmd.js +++ b/modules/ui/cmd.js @@ -1,9 +1,10 @@ -import { Detect } from '../util/detect'; +import { utilDetect } from '../util/detect'; + // Translate a MacOS key command into the appropriate Windows/Linux equivalent. // For example, ⌘Z -> Ctrl+Z -export function cmd(code) { - var detected = Detect(); +export function uiCmd(code) { + var detected = utilDetect(); if (detected.os === 'mac') { return code; diff --git a/modules/ui/commit.js b/modules/ui/commit.js index c18db4c34..94a514a61 100644 --- a/modules/ui/commit.js +++ b/modules/ui/commit.js @@ -1,16 +1,16 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; import { d3combobox } from '../lib/d3.combobox.js'; import { t } from '../util/locale'; -import { triggerEvent } from '../util/trigger_event'; +import { modeSelect } from '../modes/index'; +import { svgIcon } from '../svg/index'; import { tooltip } from '../util/tooltip'; -import { displayName, entityOrMemberSelector } from '../util/index'; -import { Icon } from '../svg/index'; -import { Select } from '../modes/index'; +import { utilDisplayName, utilEntityOrMemberSelector } from '../util/index'; +import { utilRebind } from '../util/rebind'; +import { utilTriggerEvent } from '../util/trigger_event'; -export function Commit(context) { +export function uiCommit(context) { var dispatch = d3.dispatch('cancel', 'save'); @@ -78,7 +78,7 @@ export function Commit(context) { changeSetInfo.append('a') .attr('target', '_blank') .attr('tabindex', -1) - .call(Icon('#icon-out-link', 'inline')) + .call(svgIcon('#icon-out-link', 'inline')) .attr('href', t('commit.about_changeset_comments_link')) .append('span') .text(t('commit.about_changeset_comments')); @@ -114,7 +114,7 @@ export function Commit(context) { .merge(warningLi); warningLi - .call(Icon('#icon-alert', 'pre-text')); + .call(svgIcon('#icon-alert', 'pre-text')); warningLi .append('strong') @@ -220,7 +220,7 @@ export function Commit(context) { li.each(function(d) { d3.select(this) - .call(Icon('#icon-' + d.entity.geometry(d.graph), 'pre-text ' + d.changeType)); + .call(svgIcon('#icon-' + d.entity.geometry(d.graph), 'pre-text ' + d.changeType)); }); li.append('span') @@ -234,7 +234,7 @@ export function Commit(context) { li.append('span') .attr('class', 'entity-name') .text(function(d) { - var name = displayName(d.entity) || '', + var name = utilDisplayName(d.entity) || '', string = ''; if (name !== '') string += ':'; return string += ' ' + name; @@ -247,13 +247,13 @@ export function Commit(context) { // Call checkComment off the bat, in case a changeset // comment is recovered from localStorage - triggerEvent(commentField, 'input'); + utilTriggerEvent(commentField, 'input'); function mouseover(d) { if (d.entity) { context.surface().selectAll( - entityOrMemberSelector([d.entity.id], context.graph()) + utilEntityOrMemberSelector([d.entity.id], context.graph()) ).classed('hover', true); } } @@ -269,8 +269,8 @@ export function Commit(context) { if (d.entity) { context.map().zoomTo(d.entity); context.enter( - Select(context, [d.entity.id]) - .suppressMenu(true)); + modeSelect(context, [d.entity.id]).suppressMenu(true) + ); } } @@ -281,7 +281,7 @@ export function Commit(context) { context.graph().entity(entity.id).geometry(context.graph()) !== 'vertex') { context.map().zoomTo(entity); context.surface().selectAll( - entityOrMemberSelector([entity.id], context.graph())) + utilEntityOrMemberSelector([entity.id], context.graph())) .classed('hover', true); } } @@ -303,12 +303,12 @@ export function Commit(context) { .append('a') .attr('target', '_blank') .attr('tabindex', -1) - .call(Icon('#icon-alert', 'inline')) + .call(svgIcon('#icon-alert', 'inline')) .attr('href', t('commit.google_warning_link')) .append('span') .text(t('commit.google_warning')); } } - return rebind(commit, dispatch, 'on'); + return utilRebind(commit, dispatch, 'on'); } diff --git a/modules/ui/confirm.js b/modules/ui/confirm.js index 0c178ffb0..aa1238a20 100644 --- a/modules/ui/confirm.js +++ b/modules/ui/confirm.js @@ -1,8 +1,9 @@ import { t } from '../util/locale'; -import { modal } from './modal'; +import { uiModal } from './modal'; -export function uiconfirm(selection) { - var modalSelection = modal(selection); + +export function uiConfirm(selection) { + var modalSelection = uiModal(selection); modalSelection.select('.modal') .classed('modal-alert', true); @@ -18,6 +19,7 @@ export function uiconfirm(selection) { var buttons = section.append('div') .attr('class', 'modal-section buttons cf'); + modalSelection.okButton = function() { buttons .append('button') @@ -30,5 +32,6 @@ export function uiconfirm(selection) { return modalSelection; }; + return modalSelection; } diff --git a/modules/ui/conflicts.js b/modules/ui/conflicts.js index 186222a1d..c6d023a1c 100644 --- a/modules/ui/conflicts.js +++ b/modules/ui/conflicts.js @@ -1,14 +1,16 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; import { t } from '../util/locale'; -import { Extent } from '../geo/index'; -import { Icon } from '../svg/index'; -import { entityOrMemberSelector } from '../util/index'; +import { geoExtent } from '../geo/index'; +import { svgIcon } from '../svg/index'; +import { utilEntityOrMemberSelector } from '../util/index'; +import { utilRebind } from '../util/rebind'; -export function Conflicts(context) { + +export function uiConflicts(context) { var dispatch = d3.dispatch('download', 'cancel', 'save'), list; + function conflicts(selection) { var header = selection .append('div') @@ -18,7 +20,7 @@ export function Conflicts(context) { .append('button') .attr('class', 'fr') .on('click', function() { dispatch.call('cancel'); }) - .call(Icon('#icon-close')); + .call(svgIcon('#icon-close')); header .append('h3') @@ -157,9 +159,9 @@ export function Conflicts(context) { item.exit() .remove(); - } + function addChoices(selection) { var choices = selection .append('ul') @@ -195,6 +197,7 @@ export function Conflicts(context) { }); } + function choose(ul, datum) { if (d3.event) d3.event.preventDefault(); @@ -204,7 +207,7 @@ export function Conflicts(context) { .selectAll('input') .property('checked', function(d) { return d === datum; }); - var extent = Extent(), + var extent = geoExtent(), entity; entity = context.graph().hasEntity(datum.id); @@ -218,6 +221,7 @@ export function Conflicts(context) { zoomToEntity(datum.id, extent); } + function zoomToEntity(id, extent) { context.surface().selectAll('.hover') .classed('hover', false); @@ -230,7 +234,7 @@ export function Conflicts(context) { context.map().zoomTo(entity); } context.surface().selectAll( - entityOrMemberSelector([entity.id], context.graph())) + utilEntityOrMemberSelector([entity.id], context.graph())) .classed('hover', true); } } @@ -253,5 +257,6 @@ export function Conflicts(context) { return conflicts; }; - return rebind(conflicts, dispatch, 'on'); + + return utilRebind(conflicts, dispatch, 'on'); } diff --git a/modules/ui/contributors.js b/modules/ui/contributors.js index 07cca4684..ae09a202a 100644 --- a/modules/ui/contributors.js +++ b/modules/ui/contributors.js @@ -1,14 +1,16 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { t } from '../util/locale'; -import { Icon } from '../svg/index'; +import { svgIcon } from '../svg/index'; -export function Contributors(context) { + +export function uiContributors(context) { var debouncedUpdate = _.debounce(function() { update(); }, 1000), limit = 4, hidden = false, wrap = d3.select(null); + function update() { var users = {}, entities = context.intersects(context.map().extent()); @@ -21,7 +23,7 @@ export function Contributors(context) { subset = u.slice(0, u.length > limit ? limit - 1 : limit); wrap.html('') - .call(Icon('#icon-nearby', 'pre-text light')); + .call(svgIcon('#icon-nearby', 'pre-text light')); var userList = d3.select(document.createElement('span')); @@ -67,6 +69,7 @@ export function Contributors(context) { } } + return function(selection) { wrap = selection; update(); diff --git a/modules/ui/disclosure.js b/modules/ui/disclosure.js index cbe973bfd..48498f935 100644 --- a/modules/ui/disclosure.js +++ b/modules/ui/disclosure.js @@ -1,59 +1,72 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; -import { Toggle } from './toggle'; +import { utilRebind } from '../util/rebind'; +import { uiToggle } from './toggle'; -export function Disclosure() { + +export function uiDisclosure() { var dispatch = d3.dispatch('toggled'), title, expanded = false, content = function () {}; + var disclosure = function(selection) { - var $link = selection.selectAll('.hide-toggle') + var hideToggle = selection.selectAll('.hide-toggle') .data([0]); - $link = $link.enter().append('a') + hideToggle = hideToggle.enter() + .append('a') .attr('href', '#') .attr('class', 'hide-toggle') - .merge($link); + .merge(hideToggle); - $link.text(title) + hideToggle + .text(title) .on('click', toggle) .classed('expanded', expanded); - var $body = selection.selectAll('div') + + var wrap = selection.selectAll('div') .data([0]); - $body = $body.enter().append('div').merge($body); + wrap = wrap.enter() + .append('div') + .merge(wrap); - $body.classed('hide', !expanded) + wrap + .classed('hide', !expanded) .call(content); + function toggle() { expanded = !expanded; - $link.classed('expanded', expanded); - $body.call(Toggle(expanded)); + hideToggle.classed('expanded', expanded); + div.call(Toggle(expanded)); dispatch.call('toggled', this, expanded); } }; + disclosure.title = function(_) { if (!arguments.length) return title; title = _; return disclosure; }; + disclosure.expanded = function(_) { if (!arguments.length) return expanded; expanded = _; return disclosure; }; + disclosure.content = function(_) { if (!arguments.length) return content; content = _; return disclosure; }; - return rebind(disclosure, dispatch, 'on'); + + return utilRebind(disclosure, dispatch, 'on'); } diff --git a/modules/ui/entity_editor.js b/modules/ui/entity_editor.js index af10a0862..5e7ded85f 100644 --- a/modules/ui/entity_editor.js +++ b/modules/ui/entity_editor.js @@ -1,20 +1,20 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; import { t } from '../util/locale'; import { tooltip } from '../util/tooltip'; -import { Browse } from '../modes/index'; -import { ChangeTags } from '../actions/index'; -import { Icon } from '../svg/index'; -import { PresetIcon } from './preset_icon'; -import { RawMemberEditor } from './raw_member_editor'; -import { RawMembershipEditor } from './raw_membership_editor'; -import { RawTagEditor } from './raw_tag_editor'; -import { TagReference } from './tag_reference'; -import { preset } from './preset'; +import { actionChangeTags } from '../actions/index'; +import { modeBrowse } from '../modes/index'; +import { svgIcon } from '../svg/index'; +import { uiPresetIcon } from './preset_icon'; +import { uiRawMemberEditor } from './raw_member_editor'; +import { uiRawMembershipEditor } from './raw_membership_editor'; +import { uiRawTagEditor } from './raw_tag_editor'; +import { uiTagReference } from './tag_reference'; +import { uiPreset } from './preset'; +import { utilRebind } from '../util/rebind'; -export function EntityEditor(context) { +export function uiEntityEditor(context) { var dispatch = d3.dispatch('choose'), state = 'select', coalesceChanges = false, @@ -24,9 +24,9 @@ export function EntityEditor(context) { activePreset, reference; - var presetEditor = preset(context) + var presetEditor = uiPreset(context) .on('change', changeTags); - var rawTagEditor = RawTagEditor(context) + var rawTagEditor = uiRawTagEditor(context) .on('change', changeTags); @@ -51,8 +51,8 @@ export function EntityEditor(context) { enter .append('button') .attr('class', 'fr preset-close') - .on('click', function() { context.enter(Browse(context)); }) - .call(Icon(modified ? '#icon-apply' : '#icon-close')); + .on('click', function() { context.enter(modeBrowse(context)); }) + .call(svgIcon(modified ? '#icon-apply' : '#icon-close')); enter .append('h3') @@ -108,7 +108,7 @@ export function EntityEditor(context) { .call(reference.body); body.select('.preset-list-item button') - .call(PresetIcon() + .call(uiPresetIcon() .geometry(context.geometry(id)) .preset(activePreset)); @@ -132,7 +132,7 @@ export function EntityEditor(context) { if (entity.type === 'relation') { body.select('.raw-member-editor') .style('display', 'block') - .call(RawMemberEditor(context) + .call(uiRawMemberEditor(context) .entityID(id)); } else { body.select('.raw-member-editor') @@ -140,7 +140,7 @@ export function EntityEditor(context) { } body.select('.raw-membership-editor') - .call(RawMembershipEditor(context) + .call(uiRawMembershipEditor(context) .entityID(id)); @@ -222,9 +222,9 @@ export function EntityEditor(context) { if (!_.isEqual(entity.tags, tags)) { if (coalesceChanges) { - context.overwrite(ChangeTags(id, tags), annotation); + context.overwrite(actionChangeTags(id, tags), annotation); } else { - context.perform(ChangeTags(id, tags), annotation); + context.perform(actionChangeTags(id, tags), annotation); coalesceChanges = !!onInput; } } @@ -261,12 +261,12 @@ export function EntityEditor(context) { if (!arguments.length) return activePreset; if (_ !== activePreset) { activePreset = _; - reference = TagReference(activePreset.reference(context.geometry(id)), context) + reference = uiTagReference(activePreset.reference(context.geometry(id)), context) .showing(false); } return entityEditor; }; - return rebind(entityEditor, dispatch, 'on'); + return utilRebind(entityEditor, dispatch, 'on'); } diff --git a/modules/ui/feature_info.js b/modules/ui/feature_info.js index badd9dd72..d214fb27d 100644 --- a/modules/ui/feature_info.js +++ b/modules/ui/feature_info.js @@ -1,10 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { t } from '../util/locale'; +import { uiTooltipHtml } from './tooltipHtml'; import { tooltip } from '../util/tooltip'; -import { tooltipHtml } from './tooltipHtml'; -export function FeatureInfo(context) { + +export function uiFeatureInfo(context) { function update(selection) { var features = context.features(), stats = features.stats(), @@ -23,7 +24,7 @@ export function FeatureInfo(context) { .placement('top') .html(true) .title(function() { - return tooltipHtml(hiddenList.join('
')); + return uiTooltipHtml(hiddenList.join('
')); }); var warning = selection.append('a') @@ -42,6 +43,7 @@ export function FeatureInfo(context) { .classed('hide', !hiddenList.length); } + return function(selection) { update(selection); diff --git a/modules/ui/feature_list.js b/modules/ui/feature_list.js index 36336d188..a906e6f5e 100644 --- a/modules/ui/feature_list.js +++ b/modules/ui/feature_list.js @@ -1,15 +1,17 @@ import * as d3 from 'd3'; import * as sexagesimal from 'sexagesimal'; import { t } from '../util/locale'; -import { Extent, chooseEdge } from '../geo/index'; -import { displayName, entityOrMemberSelector } from '../util/index'; -import { Entity } from '../core/index'; -import { Icon } from '../svg/index'; -import { Select } from '../modes/index'; +import { coreEntity } from '../core/index'; +import { geoExtent, geoChooseEdge } from '../geo/index'; +import { modeSelect } from '../modes/index'; +import { svgIcon } from '../svg/index'; +import { utilDisplayName, utilEntityOrMemberSelector } from '../util/index'; -export function FeatureList(context) { + +export function uiFeatureList(context) { var geocodeResults; + function featureList(selection) { var header = selection.append('div') .attr('class', 'header fillL cf'); @@ -17,19 +19,6 @@ export function FeatureList(context) { header.append('h3') .text(t('inspector.feature_list')); - function keypress() { - var q = search.property('value'), - items = list.selectAll('.feature-list-item'); - if (d3.event.keyCode === 13 && q.length && items.size()) { - click(items.datum()); - } - } - - function inputevent() { - geocodeResults = undefined; - drawList(); - } - var searchWrap = selection.append('div') .attr('class', 'search-header'); @@ -40,7 +29,7 @@ export function FeatureList(context) { .on('input', inputevent); searchWrap - .call(Icon('#icon-search', 'pre-text')); + .call(svgIcon('#icon-search', 'pre-text')); var listWrap = selection.append('div') .attr('class', 'inspector-body'); @@ -53,17 +42,35 @@ export function FeatureList(context) { context.map() .on('drawn.feature-list', mapDrawn); + + function keypress() { + var q = search.property('value'), + items = list.selectAll('.feature-list-item'); + if (d3.event.keyCode === 13 && q.length && items.size()) { + click(items.datum()); + } + } + + + function inputevent() { + geocodeResults = undefined; + drawList(); + } + + function clearSearch() { search.property('value', ''); drawList(); } + function mapDrawn(e) { if (e.full) { drawList(); } } + function features() { var entities = {}, result = [], @@ -102,7 +109,7 @@ export function FeatureList(context) { entities[entity.id] = true; - var name = displayName(entity) || ''; + var name = utilDisplayName(entity) || ''; if (name.toLowerCase().indexOf(q) >= 0) { result.push({ id: entity.id, @@ -127,12 +134,12 @@ export function FeatureList(context) { // https://github.com/openstreetmap/iD/issues/1890 if (d.osm_type && d.osm_id) { result.push({ - id: Entity.id.fromOSM(d.osm_type, d.osm_id), + id: coreEntity.id.fromOSM(d.osm_type, d.osm_id), geometry: d.osm_type === 'relation' ? 'relation' : d.osm_type === 'way' ? 'line' : 'point', type: d.type !== 'yes' ? (d.type.charAt(0).toUpperCase() + d.type.slice(1)).replace('_', ' ') : (d.class.charAt(0).toUpperCase() + d.class.slice(1)).replace('_', ' '), name: d.display_name, - extent: new Extent( + extent: new geoExtent( [parseFloat(d.boundingbox[3]), parseFloat(d.boundingbox[0])], [parseFloat(d.boundingbox[2]), parseFloat(d.boundingbox[1])]) }); @@ -142,6 +149,7 @@ export function FeatureList(context) { return result; } + function drawList() { var value = search.property('value'), results = features(); @@ -155,7 +163,7 @@ export function FeatureList(context) { .enter().append('button') .property('disabled', true) .attr('class', 'no-results-item') - .call(Icon('#icon-alert', 'pre-text')); + .call(svgIcon('#icon-alert', 'pre-text')); resultsIndicator.append('span') .attr('class', 'entity-name'); @@ -200,7 +208,7 @@ export function FeatureList(context) { label.each(function(d) { d3.select(this) - .call(Icon('#icon-' + d.geometry, 'pre-text')); + .call(svgIcon('#icon-' + d.geometry, 'pre-text')); }); label.append('span') @@ -221,18 +229,21 @@ export function FeatureList(context) { .remove(); } + function mouseover(d) { if (d.id === -1) return; - context.surface().selectAll(entityOrMemberSelector([d.id], context.graph())) + context.surface().selectAll(utilEntityOrMemberSelector([d.id], context.graph())) .classed('hover', true); } + function mouseout() { context.surface().selectAll('.hover') .classed('hover', false); } + function click(d) { d3.event.preventDefault(); if (d.location) { @@ -243,15 +254,16 @@ export function FeatureList(context) { context.map().center(d.entity.loc); } else if (d.entity.type === 'way') { var center = context.projection(context.map().center()), - edge = chooseEdge(context.childNodes(d.entity), center, context.projection); + edge = geoChooseEdge(context.childNodes(d.entity), center, context.projection); context.map().center(edge.loc); } - context.enter(Select(context, [d.entity.id]).suppressMenu(true)); + context.enter(modeSelect(context, [d.entity.id]).suppressMenu(true)); } else { context.zoomToEntity(d.id); } } + function geocode() { var searchVal = encodeURIComponent(search.property('value')); d3.json('https://nominatim.openstreetmap.org/search/' + searchVal + '?limit=10&format=json', function(err, resp) { @@ -261,5 +273,6 @@ export function FeatureList(context) { } } + return featureList; } diff --git a/modules/ui/fields/access.js b/modules/ui/fields/access.js index 737598d39..ab3065422 100644 --- a/modules/ui/fields/access.js +++ b/modules/ui/fields/access.js @@ -1,11 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../../util/rebind'; -import { getSetValue } from '../../util/get_set_value'; +import { utilRebind } from '../../util/rebind'; +import { utilGetSetValue } from '../../util/get_set_value'; import { d3combobox } from '../../lib/d3.combobox.js'; -export function access(field) { +export function uiFieldAccess(field) { var dispatch = d3.dispatch('change'), items; @@ -35,12 +35,14 @@ export function access(field) { .append('li') .attr('class', function(d) { return 'cf preset-access-' + d; }); - enter.append('span') + enter + .append('span') .attr('class', 'col6 label preset-label-access') .attr('for', function(d) { return 'preset-input-access-' + d; }) .text(function(d) { return field.t('types.' + d); }); - enter.append('div') + enter + .append('div') .attr('class', 'col6 preset-input-access-wrap') .append('input') .attr('type', 'text') @@ -64,7 +66,7 @@ export function access(field) { function change(d) { var tag = {}; - tag[d] = getSetValue(d3.select(this)) || undefined; + tag[d] = utilGetSetValue(d3.select(this)) || undefined; dispatch.call('change', this, tag); } @@ -193,8 +195,9 @@ export function access(field) { } }; + access.tags = function(tags) { - getSetValue(items.selectAll('.preset-input-access'), + utilGetSetValue(items.selectAll('.preset-input-access'), function(d) { return tags[d] || ''; }) .attr('placeholder', function() { return tags.access ? tags.access : field.placeholder(); @@ -209,10 +212,12 @@ export function access(field) { }); }; + access.focus = function() { items.selectAll('.preset-input-access') .node().focus(); }; - return rebind(access, dispatch, 'on'); + + return utilRebind(access, dispatch, 'on'); } diff --git a/modules/ui/fields/address.js b/modules/ui/fields/address.js index 51e93f7fa..a100c98b9 100644 --- a/modules/ui/fields/address.js +++ b/modules/ui/fields/address.js @@ -1,13 +1,20 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../../util/rebind'; -import { getSetValue } from '../../util/get_set_value'; import { d3combobox } from '../../lib/d3.combobox.js'; -import { Extent, chooseEdge, sphericalDistance } from '../../geo/index'; -import { nominatim } from '../../services/index'; -import { addressFormats } from '../../../data/index'; +import { dataAddressFormats } from '../../../data/index'; -export function address(field, context) { +import { + geoExtent, + geoChooseEdge, + geoSphericalDistance +} from '../../geo/index'; + +import { serviceNominatim } from '../../services/index'; +import { utilRebind } from '../../util/rebind'; +import { utilGetSetValue } from '../../util/get_set_value'; + + +export function uiFieldAddress(field, context) { var dispatch = d3.dispatch('init', 'change'), wrap, entity, @@ -25,7 +32,7 @@ export function address(field, context) { function getStreets() { var extent = entity.extent(context.graph()), l = extent.center(), - box = Extent(l).padByMeters(200); + box = geoExtent(l).padByMeters(200); return context.intersects(box) .filter(isAddressable) @@ -33,7 +40,7 @@ export function address(field, context) { var loc = context.projection([ (extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2]), - choice = chooseEdge(context.childNodes(d), loc, context.projection); + choice = geoChooseEdge(context.childNodes(d), loc, context.projection); return { title: d.tags.name, value: d.tags.name, @@ -52,7 +59,7 @@ export function address(field, context) { function getCities() { var extent = entity.extent(context.graph()), l = extent.center(), - box = Extent(l).padByMeters(200); + box = geoExtent(l).padByMeters(200); return context.intersects(box) .filter(isAddressable) @@ -60,7 +67,7 @@ export function address(field, context) { return { title: d.tags['addr:city'] || d.tags.name, value: d.tags['addr:city'] || d.tags.name, - dist: sphericalDistance(d.extent(context.graph()).center(), l) + dist: geoSphericalDistance(d.extent(context.graph()).center(), l) }; }).sort(function(a, b) { return a.dist - b.dist; @@ -87,7 +94,7 @@ export function address(field, context) { function getPostCodes() { var extent = entity.extent(context.graph()), l = extent.center(), - box = Extent(l).padByMeters(200); + box = geoExtent(l).padByMeters(200); return context.intersects(box) .filter(isAddressable) @@ -95,7 +102,7 @@ export function address(field, context) { return { title: d.tags['addr:postcode'], value: d.tags['addr:postcode'], - dist: sphericalDistance(d.extent(context.graph()).center(), l) + dist: geoSphericalDistance(d.extent(context.graph()).center(), l) }; }).sort(function(a, b) { return a.dist - b.dist; @@ -122,11 +129,11 @@ export function address(field, context) { var center = entity.extent(context.graph()).center(), addressFormat; - nominatim.init(); - nominatim.countryCode(center, function (err, countryCode) { - addressFormat = _.find(addressFormats, function (a) { + serviceNominatim.init(); + serviceNominatim.countryCode(center, function (err, countryCode) { + addressFormat = _.find(dataAddressFormats, function (a) { return a && a.countryCodes && _.includes(a.countryCodes, countryCode); - }) || _.first(addressFormats); + }) || _.first(dataAddressFormats); function row(r) { // Normalize widths. @@ -203,7 +210,7 @@ export function address(field, context) { function updateTags(tags) { - getSetValue(wrap.selectAll('input'), function (field) { + utilGetSetValue(wrap.selectAll('input'), function (field) { return tags['addr:' + field.id] || ''; }); } @@ -233,5 +240,5 @@ export function address(field, context) { }; - return rebind(address, dispatch, 'on'); + return utilRebind(address, dispatch, 'on'); } diff --git a/modules/ui/fields/check.js b/modules/ui/fields/check.js index 9b01c4641..e2baefa71 100644 --- a/modules/ui/fields/check.js +++ b/modules/ui/fields/check.js @@ -1,12 +1,12 @@ import * as d3 from 'd3'; -import { rebind } from '../../util/rebind'; +import { utilRebind } from '../../util/rebind'; import { t } from '../../util/locale'; -import { oneWayTags } from '../../core/index'; +import { coreOneWayTags } from '../../core/index'; -export { check as defaultcheck }; +export { uiFieldCheck as uiFieldDefaultcheck }; -export function check(field) { +export function uiFieldCheck(field) { var dispatch = d3.dispatch('change'), options = field.strings && field.strings.options, values = [], @@ -36,7 +36,7 @@ export function check(field) { // where implied oneway tag exists (e.g. `junction=roundabout`) #2220, #1841 if (field.id === 'oneway') { for (var key in entity.tags) { - if (key in oneWayTags && (entity.tags[key] in oneWayTags[key])) { + if (key in coreOneWayTags && (entity.tags[key] in coreOneWayTags[key])) { texts[0] = t('presets.fields.oneway_yes.options.undefined'); break; } @@ -94,5 +94,5 @@ export function check(field) { box.node().focus(); }; - return rebind(check, dispatch, 'on'); + return utilRebind(check, dispatch, 'on'); } diff --git a/modules/ui/fields/combo.js b/modules/ui/fields/combo.js index b08a9972a..35e270978 100644 --- a/modules/ui/fields/combo.js +++ b/modules/ui/fields/combo.js @@ -1,18 +1,19 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { t } from '../../util/locale'; -import { rebind } from '../../util/rebind'; -import { getSetValue } from '../../util/get_set_value'; import { d3combobox } from '../../lib/d3.combobox.js'; -import { nominatim } from '../../services/index'; +import { serviceNominatim } from '../../services/index'; +import { utilRebind } from '../../util/rebind'; +import { utilGetSetValue } from '../../util/get_set_value'; export { - combo as typeCombo, - combo as multiCombo, - combo as networkCombo + uiFieldCombo as uiFieldTypeCombo, + uiFieldCombo as uiFieldMultiCombo, + uiFieldCombo as uiFieldNetworkCombo }; -export function combo(field, context) { + +export function uiFieldCombo(field, context) { var dispatch = d3.dispatch('change'), isMulti = (field.type === 'multiCombo'), isNetwork = (field.type === 'networkCombo'), @@ -194,13 +195,13 @@ export function combo(field, context) { function change() { - var val = tagValue(getSetValue(input)), + var val = tagValue(utilGetSetValue(input)), t = {}; if (isMulti) { if (!val) return; container.classed('active', false); - getSetValue(input, ''); + utilGetSetValue(input, ''); field.keys.push(field.key + val); t[field.key + val] = 'yes'; window.setTimeout(function() { input.node().focus(); }, 10); @@ -249,8 +250,8 @@ export function combo(field, context) { if (isNetwork) { var center = entity.extent(context.graph()).center(); - nominatim.init(); - nominatim.countryCode(center, function (err, code) { + serviceNominatim.init(); + serviceNominatim.countryCode(center, function (err, code) { countryCode = code; }); } @@ -325,7 +326,7 @@ export function combo(field, context) { .text('×'); } else { - getSetValue(input, displayValue(tags[field.key])); + utilGetSetValue(input, displayValue(tags[field.key])); } }; @@ -342,5 +343,5 @@ export function combo(field, context) { }; - return rebind(combo, dispatch, 'on'); + return utilRebind(combo, dispatch, 'on'); } diff --git a/modules/ui/fields/cycleway.js b/modules/ui/fields/cycleway.js index 3f3af4c95..d6941dd6f 100644 --- a/modules/ui/fields/cycleway.js +++ b/modules/ui/fields/cycleway.js @@ -1,10 +1,10 @@ import * as d3 from 'd3'; -import { rebind } from '../../util/rebind'; -import { getSetValue } from '../../util/get_set_value'; import { d3combobox } from '../../lib/d3.combobox.js'; +import { utilRebind } from '../../util/rebind'; +import { utilGetSetValue } from '../../util/get_set_value'; -export function cycleway(field) { +export function uiFieldCycleway(field) { var dispatch = d3.dispatch('change'), items = d3.select(null); @@ -65,8 +65,8 @@ export function cycleway(field) { function change() { - var left = getSetValue(d3.select('.preset-input-cyclewayleft')), - right = getSetValue(d3.select('.preset-input-cyclewayright')), + var left = utilGetSetValue(d3.select('.preset-input-cyclewayleft')), + right = utilGetSetValue(d3.select('.preset-input-cyclewayright')), tag = {}; if (left === 'none' || left === '') { left = undefined; } @@ -104,7 +104,7 @@ export function cycleway(field) { cycleway.tags = function(tags) { - getSetValue(items.selectAll('.preset-input-cycleway'), function(d) { + utilGetSetValue(items.selectAll('.preset-input-cycleway'), function(d) { // If cycleway is set, always return that if (tags.cycleway) { return tags.cycleway; @@ -121,5 +121,5 @@ export function cycleway(field) { }; - return rebind(cycleway, dispatch, 'on'); + return utilRebind(cycleway, dispatch, 'on'); } diff --git a/modules/ui/fields/index.js b/modules/ui/fields/index.js index b7edf10a9..25c840f09 100644 --- a/modules/ui/fields/index.js +++ b/modules/ui/fields/index.js @@ -1,38 +1,54 @@ -import { check, defaultcheck} from './check'; -import { combo, multiCombo, networkCombo, typeCombo } from './combo'; -import { email, number, tel, text, url } from './input'; +import { + uiFieldCheck, + uiFieldDefaultcheck +} from './check'; -import { access } from './access'; -import { address } from './address'; -import { cycleway } from './cycleway'; -import { lanes } from './lanes'; -import { localized } from './localized'; -import { maxspeed } from './maxspeed'; -import { radio } from './radio'; -import { restrictions } from './restrictions'; -import { textarea } from './textarea'; -import { wikipedia } from './wikipedia'; +import { + uiFieldCombo, + uiFieldMultiCombo, + uiFieldNetworkCombo, + uiFieldTypeCombo +} from './combo'; -export var fields = { - access: access, - address: address, - check: check, - defaultcheck: defaultcheck, - combo: combo, - typeCombo: typeCombo, - multiCombo: multiCombo, - networkCombo: networkCombo, - cycleway: cycleway, - text: text, - url: url, - number: number, - email: email, - tel: tel, - localized: localized, - lanes: lanes, - maxspeed: maxspeed, - radio: radio, - restrictions: restrictions, - textarea: textarea, - wikipedia: wikipedia +import { + uiFieldEmail, + uiFieldNumber, + uiFieldTel, + uiFieldText, + uiFieldUrl +} from './input'; + +import { uiFieldAccess } from './access'; +import { uiFieldAddress } from './address'; +import { uiFieldCycleway } from './cycleway'; +import { uiFieldLanes } from './lanes'; +import { uiFieldLocalized } from './localized'; +import { uiFieldMaxspeed } from './maxspeed'; +import { uiFieldRadio } from './radio'; +import { uiFieldRestrictions } from './restrictions'; +import { uiFieldTextarea } from './textarea'; +import { uiFieldWikipedia } from './wikipedia'; + +export var uiFields = { + access: uiFieldAccess, + address: uiFieldAddress, + check: uiFieldCheck, + defaultcheck: uiFieldDefaultcheck, + combo: uiFieldCombo, + typeCombo: uiFieldTypeCombo, + multiCombo: uiFieldMultiCombo, + networkCombo: uiFieldNetworkCombo, + cycleway: uiFieldCycleway, + text: uiFieldText, + url: uiFieldUrl, + number: uiFieldNumber, + email: uiFieldEmail, + tel: uiFieldTel, + localized: uiFieldLocalized, + lanes: uiFieldLanes, + maxspeed: uiFieldMaxspeed, + radio: uiFieldRadio, + restrictions: uiFieldRestrictions, + textarea: uiFieldTextarea, + wikipedia: uiFieldWikipedia }; diff --git a/modules/ui/fields/input.js b/modules/ui/fields/input.js index d28990a16..b181014fe 100644 --- a/modules/ui/fields/input.js +++ b/modules/ui/fields/input.js @@ -1,18 +1,19 @@ import * as d3 from 'd3'; -import { rebind } from '../../util/rebind'; -import { getSetValue } from '../../util/get_set_value'; import { t } from '../../util/locale'; -import { nominatim as nominatimService } from '../../services/index'; -import { phoneFormats } from '../../../data/index'; +import { serviceNominatim } from '../../services/index'; +import { dataPhoneFormats } from '../../../data/index'; +import { utilRebind } from '../../util/rebind'; +import { utilGetSetValue } from '../../util/get_set_value'; export { - url as text, - url as number, - url as tel, - url as email + uiFieldText as uiFieldUrl, + uiFieldText as uiFieldNumber, + uiFieldText as uiFieldTel, + uiFieldText as uiFieldEmail }; -export function url(field, context) { + +export function uiFieldText(field, context) { var dispatch = d3.dispatch('change'), input, entity; @@ -38,11 +39,11 @@ export function url(field, context) { if (field.type === 'tel') { var center = entity.extent(context.graph()).center(); - nominatimService.init(); - nominatimService.countryCode(center, function (err, countryCode) { - if (err || !phoneFormats[countryCode]) return; + serviceNominatim.init(); + serviceNominatim.countryCode(center, function (err, countryCode) { + if (err || !dataPhoneFormats[countryCode]) return; selection.selectAll('#' + fieldId) - .attr('placeholder', phoneFormats[countryCode]); + .attr('placeholder', dataPhoneFormats[countryCode]); }); } else if (field.type === 'number') { @@ -84,7 +85,7 @@ export function url(field, context) { function change(onInput) { return function() { var t = {}; - t[field.key] = getSetValue(input) || undefined; + t[field.key] = utilGetSetValue(input) || undefined; dispatch.call('change', this, t, onInput); }; } @@ -98,7 +99,7 @@ export function url(field, context) { i.tags = function(tags) { - getSetValue(input, tags[field.key] || ''); + utilGetSetValue(input, tags[field.key] || ''); }; @@ -107,5 +108,5 @@ export function url(field, context) { if (node) node.focus(); }; - return rebind(i, dispatch, 'on'); + return utilRebind(i, dispatch, 'on'); } diff --git a/modules/ui/fields/lanes.js b/modules/ui/fields/lanes.js index a7386cf9d..c2c57b25b 100644 --- a/modules/ui/fields/lanes.js +++ b/modules/ui/fields/lanes.js @@ -1,8 +1,9 @@ import * as d3 from 'd3'; -import { rebind } from '../../util/rebind'; -import { getDimensions } from '../../util/dimensions'; +import { utilRebind } from '../../util/rebind'; +import { utilGetDimensions } from '../../util/dimensions'; -export function lanes(field, context) { + +export function uiFieldLanes(field, context) { var dispatch = d3.dispatch('change'), LANE_WIDTH = 40, LANE_HEIGHT = 200, @@ -28,7 +29,7 @@ export function lanes(field, context) { var surface = wrap.selectAll('.surface') .data([0]); - var d = getDimensions(wrap); + var d = utilGetDimensions(wrap); var freeSpace = d[0] - lanesData.lanes.length * LANE_WIDTH * 1.5 + LANE_WIDTH * 0.5; surface = surface.enter() @@ -130,5 +131,5 @@ export function lanes(field, context) { lanes.focus = function() {}; lanes.off = function() {}; - return rebind(lanes, dispatch, 'on'); + return utilRebind(lanes, dispatch, 'on'); } diff --git a/modules/ui/fields/localized.js b/modules/ui/fields/localized.js index 3b5f31fe5..7a092efe0 100644 --- a/modules/ui/fields/localized.js +++ b/modules/ui/fields/localized.js @@ -1,20 +1,20 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../../util/rebind'; -import { d3combobox } from '../../lib/d3.combobox.js'; -import { getSetValue } from '../../util/get_set_value'; import { t } from '../../util/locale'; +import { d3combobox } from '../../lib/d3.combobox.js'; +import { dataSuggestions, dataWikipedia } from '../../../data/index'; +import { serviceWikipedia } from '../../services/index'; +import { svgIcon } from '../../svg/index'; import { tooltip } from '../../util/tooltip'; -import { Detect } from '../../util/detect'; -import { Icon } from '../../svg/index'; -import { SuggestNames } from '../../util/index'; -import { wikipedia as wikipediaService } from '../../services/index'; -import { suggestions, wikipedia as wikipediaData } from '../../../data/index'; +import { utilDetect } from '../../util/detect'; +import { utilGetSetValue } from '../../util/get_set_value'; +import { utilRebind } from '../../util/rebind'; +import { utilSuggestNames } from '../../util/index'; -export function localized(field, context) { +export function uiFieldLocalized(field, context) { var dispatch = d3.dispatch('change', 'input'), - wikipedia = wikipediaService.init(), + wikipedia = serviceWikipedia.init(), input = d3.select(null), localizedInputs = d3.select(null), wikiTitles, @@ -36,7 +36,7 @@ export function localized(field, context) { if (field.id === 'name') { var preset = context.presets().match(entity, context.graph()); input.call(d3combobox().fetcher( - SuggestNames(preset, suggestions) + utilSuggestNames(preset, dataSuggestions) )); } @@ -53,7 +53,7 @@ export function localized(field, context) { .append('button') .attr('class', 'button-input-action localized-add minor') .attr('tabindex', -1) - .call(Icon('#icon-plus')) + .call(svgIcon('#icon-plus')) .call(tooltip() .title(t('translate.translate')) .placement('left')) @@ -75,7 +75,7 @@ export function localized(field, context) { function addNew() { d3.event.preventDefault(); var data = localizedInputs.selectAll('div.entry').data(); - var defaultLang = Detect().locale.toLowerCase().split('-')[0]; + var defaultLang = utilDetect().locale.toLowerCase().split('-')[0]; var langExists = _.find(data, function(datum) { return datum.lang === defaultLang;}); var isLangEn = defaultLang.indexOf('en') > -1; if (isLangEn || langExists) { @@ -89,7 +89,7 @@ export function localized(field, context) { function change(onInput) { return function() { var t = {}; - t[field.key] = getSetValue(d3.select(this)) || undefined; + t[field.key] = utilGetSetValue(d3.select(this)) || undefined; dispatch.call('change', this, t, onInput); }; } @@ -101,9 +101,9 @@ export function localized(field, context) { function changeLang(d) { - var lang = getSetValue(d3.select(this)), + var lang = utilGetSetValue(d3.select(this)), t = {}, - language = _.find(wikipediaData, function(d) { + language = _.find(dataWikipedia, function(d) { return d[0].toLowerCase() === lang.toLowerCase() || d[1].toLowerCase() === lang.toLowerCase(); }); @@ -114,7 +114,7 @@ export function localized(field, context) { t[key(d.lang)] = undefined; } - var value = getSetValue(d3.select(this.parentNode) + var value = utilGetSetValue(d3.select(this.parentNode) .selectAll('.localized-value')); if (lang && value) { @@ -131,7 +131,7 @@ export function localized(field, context) { function changeValue(d) { if (!d.lang) return; var t = {}; - t[key(d.lang)] = getSetValue(d3.select(this)) || undefined; + t[key(d.lang)] = utilGetSetValue(d3.select(this)) || undefined; dispatch.call('change', this, t); } @@ -139,7 +139,7 @@ export function localized(field, context) { function fetcher(value, cb) { var v = value.toLowerCase(); - cb(wikipediaData.filter(function(d) { + cb(dataWikipedia.filter(function(d) { return d[0].toLowerCase().indexOf(v) >= 0 || d[1].toLowerCase().indexOf(v) >= 0 || d[2].toLowerCase().indexOf(v) >= 0; @@ -191,7 +191,7 @@ export function localized(field, context) { .style('max-height','0px') .remove(); }) - .call(Icon('#operation-delete')); + .call(svgIcon('#operation-delete')); wrap .append('input') @@ -229,12 +229,12 @@ export function localized(field, context) { var entry = selection.selectAll('.entry'); - getSetValue(entry.select('.localized-lang'), function(d) { - var lang = _.find(wikipediaData, function(lang) { return lang[2] === d.lang; }); + utilGetSetValue(entry.select('.localized-lang'), function(d) { + var lang = _.find(dataWikipedia, function(lang) { return lang[2] === d.lang; }); return lang ? lang[1] : d.lang; }); - getSetValue(entry.select('.localized-value'), + utilGetSetValue(entry.select('.localized-value'), function(d) { return d.value; }); } @@ -251,7 +251,7 @@ export function localized(field, context) { } } - getSetValue(input, tags[field.key] || ''); + utilGetSetValue(input, tags[field.key] || ''); var postfixed = [], k, m; for (k in tags) { @@ -276,5 +276,5 @@ export function localized(field, context) { return localized; }; - return rebind(localized, dispatch, 'on'); + return utilRebind(localized, dispatch, 'on'); } diff --git a/modules/ui/fields/maxspeed.js b/modules/ui/fields/maxspeed.js index a8aa74b23..66866971f 100644 --- a/modules/ui/fields/maxspeed.js +++ b/modules/ui/fields/maxspeed.js @@ -1,12 +1,13 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../../util/rebind'; -import { getSetValue } from '../../util/get_set_value'; import { d3combobox } from '../../lib/d3.combobox.js'; -import { pointInPolygon } from '../../geo/index'; -import { imperial as imperialData } from '../../../data/index'; +import { dataImperial } from '../../../data/index'; +import { geoPointInPolygon } from '../../geo/index'; +import { utilRebind } from '../../util/rebind'; +import { utilGetSetValue } from '../../util/get_set_value'; -export function maxspeed(field, context) { + +export function uiFieldMaxspeed(field, context) { var dispatch = d3.dispatch('change'), entity, isImperial, @@ -40,9 +41,9 @@ export function maxspeed(field, context) { var childNodes = context.graph().childNodes(context.entity(entity.id)), loc = childNodes[~~(childNodes.length/2)].loc; - isImperial = _.some(imperialData.features, function(f) { + isImperial = _.some(dataImperial.features, function(f) { return _.some(f.geometry.coordinates, function(d) { - return pointInPolygon(loc, d); + return geoPointInPolygon(loc, d); }); }); @@ -62,8 +63,8 @@ export function maxspeed(field, context) { function changeUnits() { - isImperial = getSetValue(unitInput) === 'mph'; - getSetValue(unitInput, isImperial ? 'mph' : 'km/h'); + isImperial = utilGetSetValue(unitInput) === 'mph'; + utilGetSetValue(unitInput, isImperial ? 'mph' : 'km/h'); setSuggestions(); change(); } @@ -72,7 +73,7 @@ export function maxspeed(field, context) { function setSuggestions() { combobox.data((isImperial ? imperialValues : metricValues).map(comboValues)); - getSetValue(unitInput, isImperial ? 'mph' : 'km/h'); + utilGetSetValue(unitInput, isImperial ? 'mph' : 'km/h'); } @@ -86,7 +87,7 @@ export function maxspeed(field, context) { function change() { var tag = {}, - value = getSetValue(input); + value = utilGetSetValue(input); if (!value) { tag[field.key] = undefined; @@ -111,7 +112,7 @@ export function maxspeed(field, context) { } setSuggestions(); - getSetValue(input, value || ''); + utilGetSetValue(input, value || ''); }; @@ -125,5 +126,5 @@ export function maxspeed(field, context) { }; - return rebind(maxspeed, dispatch, 'on'); + return utilRebind(maxspeed, dispatch, 'on'); } diff --git a/modules/ui/fields/radio.js b/modules/ui/fields/radio.js index 4f8cc031f..3017df23c 100644 --- a/modules/ui/fields/radio.js +++ b/modules/ui/fields/radio.js @@ -1,9 +1,9 @@ import * as d3 from 'd3'; -import { rebind } from '../../util/rebind'; import { t } from '../../util/locale'; +import { utilRebind } from '../../util/rebind'; -export function radio(field) { +export function uiFieldRadio(field) { var dispatch = d3.dispatch('change'), placeholder = d3.select(null), labels = d3.select(null), @@ -93,5 +93,5 @@ export function radio(field) { }; - return rebind(radio, dispatch, 'on'); + return utilRebind(radio, dispatch, 'on'); } diff --git a/modules/ui/fields/restrictions.js b/modules/ui/fields/restrictions.js index 10bf073b5..872b72038 100644 --- a/modules/ui/fields/restrictions.js +++ b/modules/ui/fields/restrictions.js @@ -1,18 +1,41 @@ import * as d3 from 'd3'; import { t } from '../../util/locale'; -import { rebind } from '../../util/rebind'; -import { functor } from '../../util/index'; -import { getDimensions, setDimensions } from '../../util/dimensions'; -import { Extent, Intersection, RawMercator, Turn, inferRestriction } from '../../geo/index'; -import { Layers, Lines, Turns, Vertices } from '../../svg/index'; -import { RestrictTurn, UnrestrictTurn, } from '../../actions/index'; -import { Entity } from '../../core/index'; -import { Hover } from '../../behavior/index'; + +import { behaviorHover } from '../../behavior/index'; +import { coreEntity } from '../../core/index'; + +import { + actionRestrictTurn, + actionUnrestrictTurn +} from '../../actions/index'; + +import { + geoExtent, + geoIntersection, + geoRawMercator, + geoTurn, + geoInferRestriction +} from '../../geo/index'; + +import { + svgLayers, + svgLines, + svgTurns, + svgVertices +} from '../../svg/index'; + +import { utilRebind } from '../../util/rebind'; +import { utilFunctor } from '../../util/index'; + +import { + utilGetDimensions, + utilSetDimensions +} from '../../util/dimensions'; -export function restrictions(field, context) { +export function uiFieldRestrictions(field, context) { var dispatch = d3.dispatch('change'), - hover = Hover(context), + hover = behaviorHover(context), vertexID, fromNodeID; @@ -36,14 +59,14 @@ export function restrictions(field, context) { .attr('class', 'restriction-help'); - var intersection = Intersection(context.graph(), vertexID), + var intersection = geoIntersection(context.graph(), vertexID), graph = intersection.graph, vertex = graph.entity(vertexID), - filter = functor(true), - extent = Extent(), - projection = RawMercator(); + filter = utilFunctor(true), + extent = geoExtent(), + projection = geoRawMercator(); - var d = getDimensions(wrap), + var d = utilGetDimensions(wrap), c = [d[0] / 2, d[1] / 2], z = 24; @@ -56,10 +79,10 @@ export function restrictions(field, context) { .translate([c[0] - s[0], c[1] - s[1]]) .clipExtent([[0, 0], d]); - var drawLayers = Layers(projection, context).only('osm').dimensions(d), - drawVertices = Vertices(projection, context), - drawLines = Lines(projection, context), - drawTurns = Turns(projection, context); + var drawLayers = svgLayers(projection, context).only('osm').dimensions(d), + drawVertices = svgVertices(projection, context), + drawLines = svgLines(projection, context), + drawTurns = svgTurns(projection, context); enter .call(drawLayers) @@ -71,7 +94,7 @@ export function restrictions(field, context) { .merge(enter); var surface = wrap.selectAll('.surface') - .call(setDimensions, d) + .call(utilSetDimensions, d) .call(drawVertices, graph, [vertex], filter, extent, z) .call(drawLines, graph, intersection.ways, filter) .call(drawTurns, graph, intersection.turns(fromNodeID)); @@ -98,25 +121,27 @@ export function restrictions(field, context) { d3.select(window) .on('resize.restrictions', function() { - setDimensions(wrap, null); + utilSetDimensions(wrap, null); render(); }); function click() { var datum = d3.event.target.__data__; - if (datum instanceof Entity) { + if (datum instanceof coreEntity) { fromNodeID = intersection.adjacentNodeId(datum.id); render(); - } else if (datum instanceof Turn) { + } else if (datum instanceof geoTurn) { if (datum.restriction) { context.perform( - UnrestrictTurn(datum, projection), - t('operations.restriction.annotation.delete')); + actionUnrestrictTurn(datum, projection), + t('operations.restriction.annotation.delete') + ); } else { context.perform( - RestrictTurn(datum, projection), - t('operations.restriction.annotation.create')); + actionRestrictTurn(datum, projection), + t('operations.restriction.annotation.create') + ); } } } @@ -124,7 +149,7 @@ export function restrictions(field, context) { function mouseover() { var datum = d3.event.target.__data__; - if (datum instanceof Turn) { + if (datum instanceof geoTurn) { var graph = context.graph(), presets = context.presets(), preset; @@ -133,18 +158,21 @@ export function restrictions(field, context) { preset = presets.match(graph.entity(datum.restriction), graph); } else { preset = presets.item('type/restriction/' + - inferRestriction( + geoInferRestriction( graph, datum.from, datum.via, datum.to, - projection)); + projection + ) + ); } wrap.selectAll('.restriction-help') .text(t('operations.restriction.help.' + (datum.restriction ? 'toggle_off' : 'toggle_on'), - { restriction: preset.name() })); + { restriction: preset.name() }) + ); } } @@ -152,7 +180,8 @@ export function restrictions(field, context) { function mouseout() { wrap.selectAll('.restriction-help') .text(t('operations.restriction.help.' + - (fromNodeID ? 'toggle' : 'select'))); + (fromNodeID ? 'toggle' : 'select')) + ); } @@ -191,5 +220,5 @@ export function restrictions(field, context) { }; - return rebind(restrictions, dispatch, 'on'); + return utilRebind(restrictions, dispatch, 'on'); } diff --git a/modules/ui/fields/textarea.js b/modules/ui/fields/textarea.js index 53e1397f8..3c627e4e8 100644 --- a/modules/ui/fields/textarea.js +++ b/modules/ui/fields/textarea.js @@ -1,10 +1,10 @@ import * as d3 from 'd3'; -import { getSetValue } from '../../util/get_set_value'; -import { rebind } from '../../util/rebind'; import { t } from '../../util/locale'; +import { utilGetSetValue } from '../../util/get_set_value'; +import { utilRebind } from '../../util/rebind'; -export function textarea(field) { +export function uiFieldTextarea(field) { var dispatch = d3.dispatch('change'), input = d3.select(null); @@ -28,14 +28,14 @@ export function textarea(field) { function change(onInput) { return function() { var t = {}; - t[field.key] = getSetValue(input) || undefined; + t[field.key] = utilGetSetValue(input) || undefined; dispatch.call('change', this, t, onInput); }; } textarea.tags = function(tags) { - getSetValue(input, tags[field.key] || ''); + utilGetSetValue(input, tags[field.key] || ''); }; @@ -44,5 +44,5 @@ export function textarea(field) { }; - return rebind(textarea, dispatch, 'on'); + return utilRebind(textarea, dispatch, 'on'); } diff --git a/modules/ui/fields/wikipedia.js b/modules/ui/fields/wikipedia.js index e4305b232..ebcf29169 100644 --- a/modules/ui/fields/wikipedia.js +++ b/modules/ui/fields/wikipedia.js @@ -1,24 +1,25 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { d3combobox } from '../../lib/d3.combobox.js'; -import { getSetValue } from '../../util/get_set_value'; -import { rebind } from '../../util/rebind'; import { t } from '../../util/locale'; -import { ChangeTags } from '../../actions/index'; -import { Detect } from '../../util/detect'; -import { Icon } from '../../svg/index'; -import { wikipedia as wikipediaData } from '../../../data/index'; +import { actionChangeTags } from '../../actions/index'; +import { d3combobox } from '../../lib/d3.combobox.js'; +import { dataWikipedia } from '../../../data/index'; import { - wikipedia as wikipediaService, - wikidata as wikidataService + serviceWikipedia, + serviceWikidata } from '../../services/index'; +import { svgIcon } from '../../svg/index'; +import { utilDetect } from '../../util/detect'; +import { utilGetSetValue } from '../../util/get_set_value'; +import { utilRebind } from '../../util/rebind'; -export function wikipedia(field, context) { + +export function uiFieldWikipedia(field, context) { var dispatch = d3.dispatch('change'), - wikipedia = wikipediaService.init(), - wikidata = wikidataService.init(), + wikipedia = serviceWikipedia.init(), + wikidata = serviceWikidata.init(), link = d3.select(null), lang = d3.select(null), title = d3.select(null), @@ -30,7 +31,7 @@ export function wikipedia(field, context) { .fetcher(function(value, cb) { var v = value.toLowerCase(); - cb(wikipediaData.filter(function(d) { + cb(dataWikipedia.filter(function(d) { return d[0].toLowerCase().indexOf(v) >= 0 || d[1].toLowerCase().indexOf(v) >= 0 || d[2].toLowerCase().indexOf(v) >= 0; @@ -64,7 +65,7 @@ export function wikipedia(field, context) { .attr('placeholder', t('translate.localized_translation_language')) .merge(lang); - getSetValue(lang, language()[1]); + utilGetSetValue(lang, language()[1]); lang .call(langcombo) @@ -96,16 +97,16 @@ export function wikipedia(field, context) { .attr('class', 'wiki-link button-input-action minor') .attr('tabindex', -1) .attr('target', '_blank') - .call(Icon('#icon-out-link', 'inline')) + .call(svgIcon('#icon-out-link', 'inline')) .merge(link); } function language() { - var value = getSetValue(lang).toLowerCase(); - var locale = Detect().locale.toLowerCase(); + var value = utilGetSetValue(lang).toLowerCase(); + var locale = utilDetect().locale.toLowerCase(); var localeLanguage; - return _.find(wikipediaData, function(d) { + return _.find(dataWikipedia, function(d) { if (d[2] === locale) localeLanguage = d; return d[0].toLowerCase() === value || d[1].toLowerCase() === value || @@ -115,7 +116,7 @@ export function wikipedia(field, context) { function changeLang() { - getSetValue(lang, language()[1]); + utilGetSetValue(lang, language()[1]); change(true); } @@ -126,9 +127,9 @@ export function wikipedia(field, context) { function change(skipWikidata) { - var value = getSetValue(title), + var value = utilGetSetValue(title), m = value.match(/https?:\/\/([-a-z]+)\.wikipedia\.org\/(?:wiki|\1-[-a-z]+)\/([^#]+)(?:#(.+))?/), - l = m && _.find(wikipediaData, function(d) { return m[1] === d[2]; }), + l = m && _.find(dataWikipedia, function(d) { return m[1] === d[2]; }), anchor, syncTags = {}; @@ -145,8 +146,8 @@ export function wikipedia(field, context) { value += '#' + anchor.replace(/_/g, ' '); } value = value.slice(0, 1).toUpperCase() + value.slice(1); - getSetValue(lang, l[1]); - getSetValue(title, value); + utilGetSetValue(lang, l[1]); + utilGetSetValue(title, value); } syncTags.wikipedia = value ? language()[2] + ':' + value : undefined; @@ -186,7 +187,7 @@ export function wikipedia(field, context) { return id.match(/^Q\d+$/); }); - context.overwrite(ChangeTags(currEntityId, currTags), annotation); + context.overwrite(actionChangeTags(currEntityId, currTags), annotation); dispatch.call('change', this, currTags); }); } @@ -195,13 +196,13 @@ export function wikipedia(field, context) { wiki.tags = function(tags) { var value = tags[field.key] || '', m = value.match(/([^:]+):([^#]+)(?:#(.+))?/), - l = m && _.find(wikipediaData, function(d) { return m[1] === d[2]; }), + l = m && _.find(dataWikipedia, function(d) { return m[1] === d[2]; }), anchor = m && m[3]; // value in correct format if (l) { - getSetValue(lang, l[1]); - getSetValue(title, m[2] + (anchor ? ('#' + anchor) : '')); + utilGetSetValue(lang, l[1]); + utilGetSetValue(title, m[2] + (anchor ? ('#' + anchor) : '')); if (anchor) { try { // Best-effort `anchorencode:` implementation @@ -215,9 +216,9 @@ export function wikipedia(field, context) { // unrecognized value format } else { - getSetValue(title, value); + utilGetSetValue(title, value); if (value && value !== '') { - getSetValue(lang, ''); + utilGetSetValue(lang, ''); } link.attr('href', 'https://en.wikipedia.org/wiki/Special:Search?search=' + value); } @@ -236,5 +237,5 @@ export function wikipedia(field, context) { }; - return rebind(wiki, dispatch, 'on'); + return utilRebind(wiki, dispatch, 'on'); } diff --git a/modules/ui/flash.js b/modules/ui/flash.js index 4e9e43f8e..6abcf4a98 100644 --- a/modules/ui/flash.js +++ b/modules/ui/flash.js @@ -1,21 +1,26 @@ -import { modal } from './modal'; +import { uiModal } from './modal'; -export function flash(selection) { - var modalSelection = modal(selection); - modalSelection.select('.modal').classed('modal-flash', true); +export function uiFlash(selection) { + var modalSelection = uiModal(selection); + + modalSelection.select('.modal') + .classed('modal-flash', true); modalSelection.select('.content') .classed('modal-section', true) .append('div') .attr('class', 'description'); - modalSelection.on('click.flash', function() { modalSelection.remove(); }); + modalSelection.on('click.flash', function() { + modalSelection.remove(); + }); setTimeout(function() { modalSelection.remove(); return true; }, 1500); + return modalSelection; } diff --git a/modules/ui/full_screen.js b/modules/ui/full_screen.js index 5d98804ff..be7cfb2f8 100644 --- a/modules/ui/full_screen.js +++ b/modules/ui/full_screen.js @@ -1,45 +1,53 @@ import * as d3 from 'd3'; import { d3keybinding } from '../lib/d3.keybinding.js'; -import { cmd } from './cmd'; +import { uiCmd } from './cmd'; -export function FullScreen(context) { + +export function uiFullScreen(context) { var element = context.container().node(), keybinding = d3keybinding('full-screen'); // button; + function getFullScreenFn() { if (element.requestFullscreen) { return element.requestFullscreen; } else if (element.msRequestFullscreen) { - return element.msRequestFullscreen; + return element.msRequestFullscreen; } else if (element.mozRequestFullScreen) { - return element.mozRequestFullScreen; + return element.mozRequestFullScreen; } else if (element.webkitRequestFullscreen) { return element.webkitRequestFullscreen; } } + function getExitFullScreenFn() { if (document.exitFullscreen) { return document.exitFullscreen; } else if (document.msExitFullscreen) { - return document.msExitFullscreen; + return document.msExitFullscreen; } else if (document.mozCancelFullScreen) { - return document.mozCancelFullScreen; + return document.mozCancelFullScreen; } else if (document.webkitExitFullscreen) { return document.webkitExitFullscreen; } } + function isFullScreen() { - return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || + return document.fullscreenElement || + document.mozFullScreenElement || + document.webkitFullscreenElement || document.msFullscreenElement; } + function isSupported() { return !!getFullScreenFn(); } + function fullScreen() { d3.event.preventDefault(); if (!isFullScreen()) { @@ -51,6 +59,7 @@ export function FullScreen(context) { } } + return function() { // selection) { if (!isSupported()) return; @@ -66,7 +75,7 @@ export function FullScreen(context) { keybinding .on('f11', fullScreen) - .on(cmd('⌘⇧F'), fullScreen); + .on(uiCmd('⌘⇧F'), fullScreen); d3.select(document) .call(keybinding); diff --git a/modules/ui/geolocate.js b/modules/ui/geolocate.js index 9084b8ceb..61e4d7d8f 100644 --- a/modules/ui/geolocate.js +++ b/modules/ui/geolocate.js @@ -1,17 +1,19 @@ import { t } from '../util/locale'; import { tooltip } from '../util/tooltip'; -import { Browse } from '../modes/index'; -import { Extent } from '../geo/index'; -import { Icon } from '../svg/index'; -import { Loading } from './loading'; +import { modeBrowse } from '../modes/index'; +import { geoExtent } from '../geo/index'; +import { svgIcon } from '../svg/index'; +import { uiLoading } from './loading'; -export function Geolocate(context) { + +export function uiGeolocate(context) { var geoOptions = { enableHighAccuracy: false, timeout: 6000 /* 6sec */ }, - locating = Loading(context).message(t('geolocate.locating')).blocking(true), + locating = uiLoading(context).message(t('geolocate.locating')).blocking(true), timeoutId; + function click() { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); context.container().call(locating); navigator.geolocation.getCurrentPosition(success, error, geoOptions); @@ -20,34 +22,38 @@ export function Geolocate(context) { timeoutId = setTimeout(finish, 10000 /* 10sec */ ); } + function success(position) { var map = context.map(), - extent = Extent([position.coords.longitude, position.coords.latitude]) + extent = geoExtent([position.coords.longitude, position.coords.latitude]) .padByMeters(position.coords.accuracy); map.centerZoom(extent.center(), Math.min(20, map.extentZoom(extent))); finish(); } + function error() { finish(); } + function finish() { locating.close(); // unblock ui if (timeoutId) { clearTimeout(timeoutId); } timeoutId = undefined; } + return function(selection) { if (!navigator.geolocation) return; - selection.append('button') + selection + .append('button') .attr('tabindex', -1) .attr('title', t('geolocate.title')) .on('click', click) - .call(Icon('#icon-geolocate', 'light')) - .call(tooltip() - .placement('left')); + .call(svgIcon('#icon-geolocate', 'light')) + .call(tooltip().placement('left')); }; } diff --git a/modules/ui/help.js b/modules/ui/help.js index 19198362d..1961880f5 100644 --- a/modules/ui/help.js +++ b/modules/ui/help.js @@ -2,12 +2,13 @@ import * as d3 from 'd3'; import marked from 'marked'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; +import { svgIcon } from '../svg/index'; +import { uiIntro } from './intro/index'; +import { uiTooltipHtml } from './tooltipHtml'; import { tooltip } from '../util/tooltip'; -import { Icon } from '../svg/index'; -import { intro } from './intro/index'; -import { tooltipHtml } from './tooltipHtml'; -export function Help(context) { + +export function uiHelp(context) { var key = 'H'; var docKeys = [ @@ -29,18 +30,21 @@ export function Help(context) { }; }); + function help(selection) { function hide() { setVisible(false); } + function toggle() { if (d3.event) d3.event.preventDefault(); tooltipBehavior.hide(button); setVisible(!button.classed('active')); } + function setVisible(show) { if (show !== shown) { button.classed('active', show); @@ -68,6 +72,7 @@ export function Help(context) { } } + function clickHelp(d, i) { pane.property('scrollTop', 0); doctitle.html(d.title); @@ -98,8 +103,9 @@ export function Help(context) { } } + function clickWalkthrough() { - d3.select(document.body).call(intro(context)); + d3.select(document.body).call(uiIntro(context)); setVisible(false); } @@ -109,11 +115,11 @@ export function Help(context) { tooltipBehavior = tooltip() .placement('left') .html(true) - .title(tooltipHtml(t('help.title'), key)), + .title(uiTooltipHtml(t('help.title'), key)), button = selection.append('button') .attr('tabindex', -1) .on('click', toggle) - .call(Icon('#icon-help', 'light')) + .call(svgIcon('#icon-help', 'light')) .call(tooltipBehavior), shown = false; diff --git a/modules/ui/index.js b/modules/ui/index.js index b7f41302d..8a51fdf98 100644 --- a/modules/ui/index.js +++ b/modules/ui/index.js @@ -1,51 +1,51 @@ -export { init } from './init'; -export { fields } from './fields/index'; -export { Account } from './account'; -export { Attribution } from './attribution'; -export { Background } from './background'; -export { cmd } from './cmd'; -export { Commit } from './commit'; -export { uiconfirm } from './confirm'; -export { Conflicts } from './conflicts'; -export { Contributors } from './contributors'; -export { Disclosure } from './disclosure'; -export { EntityEditor } from './entity_editor'; -export { FeatureInfo } from './feature_info'; -export { FeatureList } from './feature_list'; -export { flash } from './flash'; -export { FullScreen } from './full_screen'; -export { Geolocate } from './geolocate'; -export { Help } from './help'; -export { Info } from './info'; -export { Inspector } from './inspector'; -export { intro } from './intro'; -export { Lasso } from './lasso'; -export { Loading } from './loading'; -export { MapData } from './map_data'; -export { MapInMap } from './map_in_map'; -export { modal } from './modal'; -export { Modes } from './modes'; -export { Notice } from './notice'; -export { preset } from './preset'; -export { PresetIcon } from './preset_icon'; -export { PresetList } from './preset_list'; -export { RadialMenu } from './radial_menu'; -export { RawMemberEditor } from './raw_member_editor'; -export { RawMembershipEditor } from './raw_membership_editor'; -export { RawTagEditor } from './raw_tag_editor'; -export { Restore } from './restore'; -export { Save } from './save'; -export { Scale } from './scale'; -export { SelectionList } from './selection_list'; -export { Sidebar } from './sidebar'; -export { SourceSwitch } from './source_switch'; -export { Spinner } from './spinner'; -export { Splash } from './splash'; -export { Status } from './status'; -export { Success } from './success'; -export { TagReference } from './tag_reference'; -export { Toggle } from './toggle'; -export { tooltipHtml } from './tooltipHtml'; -export { UndoRedo } from './undo_redo'; -export { ViewOnOSM } from './view_on_osm'; -export { Zoom } from './zoom'; +export { uiInit } from './init'; +export { uiFields } from './fields/index'; +export { uiAccount } from './account'; +export { uiAttribution } from './attribution'; +export { uiBackground } from './background'; +export { uiCmd } from './cmd'; +export { uiCommit } from './commit'; +export { uiConfirm } from './confirm'; +export { uiConflicts } from './conflicts'; +export { uiContributors } from './contributors'; +export { uiDisclosure } from './disclosure'; +export { uiEntityEditor } from './entity_editor'; +export { uiFeatureInfo } from './feature_info'; +export { uiFeatureList } from './feature_list'; +export { uiFlash } from './flash'; +export { uiFullScreen } from './full_screen'; +export { uiGeolocate } from './geolocate'; +export { uiHelp } from './help'; +export { uiInfo } from './info'; +export { uiInspector } from './inspector'; +export { uiIntro } from './intro'; +export { uiLasso } from './lasso'; +export { uiLoading } from './loading'; +export { uiMapData } from './map_data'; +export { uiMapInMap } from './map_in_map'; +export { uiModal } from './modal'; +export { uiModes } from './modes'; +export { uiNotice } from './notice'; +export { uiPreset } from './preset'; +export { uiPresetIcon } from './preset_icon'; +export { uiPresetList } from './preset_list'; +export { uiRadialMenu } from './radial_menu'; +export { uiRawMemberEditor } from './raw_member_editor'; +export { uiRawMembershipEditor } from './raw_membership_editor'; +export { uiRawTagEditor } from './raw_tag_editor'; +export { uiRestore } from './restore'; +export { uiSave } from './save'; +export { uiScale } from './scale'; +export { uiSelectionList } from './selection_list'; +export { uiSidebar } from './sidebar'; +export { uiSourceSwitch } from './source_switch'; +export { uiSpinner } from './spinner'; +export { uiSplash } from './splash'; +export { uiStatus } from './status'; +export { uiSuccess } from './success'; +export { uiTagReference } from './tag_reference'; +export { uiToggle } from './toggle'; +export { uiTooltipHtml } from './tooltipHtml'; +export { uiUndoRedo } from './undo_redo'; +export { uiViewOnOSM } from './view_on_osm'; +export { uiZoom } from './zoom'; diff --git a/modules/ui/info.js b/modules/ui/info.js index 88cce7ccb..6af8d8ab7 100644 --- a/modules/ui/info.js +++ b/modules/ui/info.js @@ -2,15 +2,14 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; -import { Detect } from '../util/detect'; -import { Extent } from '../geo/index'; -import { cmd } from './cmd'; +import { geoExtent } from '../geo/index'; +import { utilDetect } from '../util/detect'; +import { uiCmd } from './cmd'; - -export function Info(context) { - var key = cmd('⌘I'), - isImperial = (Detect().locale.toLowerCase() === 'en-us'), +export function uiInfo(context) { + var key = uiCmd('⌘I'), + isImperial = (utilDetect().locale.toLowerCase() === 'en-us'), isHidden = true; @@ -23,7 +22,7 @@ export function Info(context) { function steradiansToSqmeters(r) { // http://gis.stackexchange.com/a/124857/40446 - return r / 12.56637 * 510065621724000; + return r / (4 * Math.PI) * 510065621724000; } @@ -116,7 +115,7 @@ export function Info(context) { var resolver = context.graph(), selected = _.filter(context.selectedIDs(), function(e) { return context.hasEntity(e); }), singular = selected.length === 1 ? selected[0] : null, - extent = Extent(), + extent = geoExtent(), entity; wrap.html(''); diff --git a/modules/ui/init.js b/modules/ui/init.js index 903238d24..c5d7f11fb 100644 --- a/modules/ui/init.js +++ b/modules/ui/init.js @@ -1,191 +1,221 @@ import * as d3 from 'd3'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; -import { setDimensions } from '../util/dimensions'; import { tooltip } from '../util/tooltip'; -import { Defs, Icon } from '../svg/index'; -import { Account } from './account'; -import { Attribution } from './attribution'; -import { Background } from './background'; -import { Browse } from '../modes/index'; -import { Contributors } from './contributors'; -import { Detect } from '../util/detect'; -import { FeatureInfo } from './feature_info'; -import { FullScreen } from './full_screen'; -import { Geolocate } from './geolocate'; -import { Hash } from '../behavior/index'; -import { Help } from './help'; -import { Info } from './info'; -import { Loading } from './loading'; -import { MapData } from './map_data'; -import { MapInMap } from './map_in_map'; -import { Modes } from './modes'; -import { Restore } from './restore'; -import { Save } from './save'; -import { Scale } from './scale'; -import { Sidebar } from './sidebar'; -import { Spinner } from './spinner'; -import { Splash } from './splash'; -import { Status } from './status'; -import { UndoRedo } from './undo_redo'; -import { Zoom } from './zoom'; -import { cmd } from './cmd'; +import { utilDetect } from '../util/detect'; +import { utilSetDimensions } from '../util/dimensions'; + +import { svgDefs, svgIcon } from '../svg/index'; +import { modeBrowse } from '../modes/index'; +import { behaviorHash } from '../behavior/index'; + +import { uiAccount } from './account'; +import { uiAttribution } from './attribution'; +import { uiBackground } from './background'; +import { uiContributors } from './contributors'; +import { uiFeatureInfo } from './feature_info'; +import { uiFullScreen } from './full_screen'; +import { uiGeolocate } from './geolocate'; +import { uiHelp } from './help'; +import { uiInfo } from './info'; +import { uiLoading } from './loading'; +import { uiMapData } from './map_data'; +import { uiMapInMap } from './map_in_map'; +import { uiModes } from './modes'; +import { uiRestore } from './restore'; +import { uiSave } from './save'; +import { uiScale } from './scale'; +import { uiSidebar } from './sidebar'; +import { uiSpinner } from './spinner'; +import { uiSplash } from './splash'; +import { uiStatus } from './status'; +import { uiUndoRedo } from './undo_redo'; +import { uiZoom } from './zoom'; +import { uiCmd } from './cmd'; -export function init(context) { +export function uiInit(context) { + function render(container) { var map = context.map(); - if (Detect().opera) container.classed('opera', true); + if (utilDetect().opera) { + container.classed('opera', true); + } - var hash = Hash(context); + var hash = behaviorHash(context); hash(); if (!hash.hadHash) { map.centerZoom([0, 0], 2); } - container.append('svg') + container + .append('svg') .attr('id', 'defs') - .call(Defs(context)); + .call(svgDefs(context)); - container.append('div') + container + .append('div') .attr('id', 'sidebar') .attr('class', 'col4') .call(ui.sidebar); - var content = container.append('div') + var content = container + .append('div') .attr('id', 'content'); - var bar = content.append('div') + var bar = content + .append('div') .attr('id', 'bar') .attr('class', 'fillD'); - content.append('div') + content + .append('div') .attr('id', 'map') .call(map); content - .call(MapInMap(context)); + .call(uiMapInMap(context)); - content.append('div') - .call(Info(context)); + content + .append('div') + .call(uiInfo(context)); - bar.append('div') + bar + .append('div') .attr('class', 'spacer col4'); var limiter = bar.append('div') .attr('class', 'limiter'); - limiter.append('div') + limiter + .append('div') .attr('class', 'button-wrap joined col3') - .call(Modes(context), limiter); + .call(uiModes(context), limiter); - limiter.append('div') + limiter + .append('div') .attr('class', 'button-wrap joined col1') - .call(UndoRedo(context)); + .call(uiUndoRedo(context)); - limiter.append('div') + limiter + .append('div') .attr('class', 'button-wrap col1') - .call(Save(context)); + .call(uiSave(context)); - bar.append('div') + bar + .append('div') .attr('class', 'full-screen') - .call(FullScreen(context)); + .call(uiFullScreen(context)); - bar.append('div') + bar + .append('div') .attr('class', 'spinner') - .call(Spinner(context)); + .call(uiSpinner(context)); - var controls = bar.append('div') + var controls = bar + .append('div') .attr('class', 'map-controls'); - controls.append('div') + controls + .append('div') .attr('class', 'map-control zoombuttons') - .call(Zoom(context)); + .call(uiZoom(context)); - controls.append('div') + controls + .append('div') .attr('class', 'map-control geolocate-control') - .call(Geolocate(context)); + .call(uiGeolocate(context)); - controls.append('div') + controls + .append('div') .attr('class', 'map-control background-control') - .call(Background(context)); + .call(uiBackground(context)); - controls.append('div') + controls + .append('div') .attr('class', 'map-control map-data-control') - .call(MapData(context)); + .call(uiMapData(context)); - controls.append('div') + controls + .append('div') .attr('class', 'map-control help-control') - .call(Help(context)); + .call(uiHelp(context)); - var about = content.append('div') + var about = content + .append('div') .attr('id', 'about'); - about.append('div') + about + .append('div') .attr('id', 'attrib') - .call(Attribution(context)); + .call(uiAttribution(context)); - var footer = about.append('div') + var footer = about + .append('div') .attr('id', 'footer') .attr('class', 'fillD'); - footer.append('div') + footer + .append('div') .attr('class', 'api-status') - .call(Status(context)); + .call(uiStatus(context)); - footer.append('div') + footer + .append('div') .attr('id', 'scale-block') - .call(Scale(context)); + .call(uiScale(context)); - var aboutList = footer.append('div') + var aboutList = footer + .append('div') .attr('id', 'info-block') .append('ul') .attr('id', 'about-list'); if (!context.embed()) { - aboutList.call(Account(context)); + aboutList.call(uiAccount(context)); } - aboutList.append('li') + aboutList + .append('li') .append('a') .attr('target', '_blank') .attr('tabindex', -1) .attr('href', 'https://github.com/openstreetmap/iD') .text(context.version); - var issueLinks = aboutList.append('li'); + var issueLinks = aboutList + .append('li'); - issueLinks.append('a') + issueLinks + .append('a') .attr('target', '_blank') .attr('tabindex', -1) .attr('href', 'https://github.com/openstreetmap/iD/issues') - .call(Icon('#icon-bug', 'light')) - .call(tooltip() - .title(t('report_a_bug')) - .placement('top') - ); + .call(svgIcon('#icon-bug', 'light')) + .call(tooltip().title(t('report_a_bug')).placement('top')); - issueLinks.append('a') + issueLinks + .append('a') .attr('target', '_blank') .attr('tabindex', -1) .attr('href', 'https://github.com/openstreetmap/iD/blob/master/CONTRIBUTING.md#translating') - .call(Icon('#icon-translate', 'light')) - .call(tooltip() - .title(t('help_translate')) - .placement('top') - ); + .call(svgIcon('#icon-translate', 'light')) + .call(tooltip().title(t('help_translate')).placement('top')); - aboutList.append('li') + aboutList + .append('li') .attr('class', 'feature-warning') .attr('tabindex', -1) - .call(FeatureInfo(context)); + .call(uiFeatureInfo(context)); - aboutList.append('li') + aboutList + .append('li') .attr('class', 'user-list') .attr('tabindex', -1) - .call(Contributors(context)); + .call(uiContributors(context)); + window.onbeforeunload = function() { return context.save(); @@ -198,11 +228,13 @@ export function init(context) { var mapDimensions = map.dimensions(); function onResize() { - mapDimensions = setDimensions(content, null); + mapDimensions = utilSetDimensions(content, null); map.dimensions(mapDimensions); } - d3.select(window).on('resize.editor', onResize); + d3.select(window) + .on('resize.editor', onResize); + onResize(); function pan(d) { @@ -227,21 +259,21 @@ export function init(context) { .on('⇧↑', pan([0, mapDimensions[1]])) .on('⇧→', pan([-mapDimensions[0], 0])) .on('⇧↓', pan([0, -mapDimensions[1]])) - .on(cmd('⌘←'), pan([mapDimensions[0], 0])) - .on(cmd('⌘↑'), pan([0, mapDimensions[1]])) - .on(cmd('⌘→'), pan([-mapDimensions[0], 0])) - .on(cmd('⌘↓'), pan([0, -mapDimensions[1]])); + .on(uiCmd('⌘←'), pan([mapDimensions[0], 0])) + .on(uiCmd('⌘↑'), pan([0, mapDimensions[1]])) + .on(uiCmd('⌘→'), pan([-mapDimensions[0], 0])) + .on(uiCmd('⌘↓'), pan([0, -mapDimensions[1]])); d3.select(document) .call(keybinding); - context.enter(Browse(context)); + context.enter(modeBrowse(context)); context.container() - .call(Splash(context)) - .call(Restore(context)); + .call(uiSplash(context)) + .call(uiRestore(context)); - var authenticating = Loading(context) + var authenticating = uiLoading(context) .message(t('loading_auth')); context.connection() @@ -254,6 +286,7 @@ export function init(context) { }); } + function ui(node) { var container = d3.select(node); context.container(container); @@ -262,7 +295,7 @@ export function init(context) { }); } - ui.sidebar = Sidebar(context); + ui.sidebar = uiSidebar(context); return ui; } diff --git a/modules/ui/inspector.js b/modules/ui/inspector.js index 98fba062b..458e06694 100644 --- a/modules/ui/inspector.js +++ b/modules/ui/inspector.js @@ -1,16 +1,17 @@ import * as d3 from 'd3'; -import { EntityEditor } from './entity_editor'; -import { PresetList } from './preset_list'; -import { ViewOnOSM } from './view_on_osm'; +import { uiEntityEditor } from './entity_editor'; +import { uiPresetList } from './preset_list'; +import { uiViewOnOSM } from './view_on_osm'; -export function Inspector(context) { - var presetList = PresetList(context), - entityEditor = EntityEditor(context), +export function uiInspector(context) { + var presetList = uiPresetList(context), + entityEditor = uiEntityEditor(context), state = 'select', entityID, newFeature = false; + function inspector(selection) { presetList .entityID(entityID) @@ -64,7 +65,7 @@ export function Inspector(context) { .merge(footer); footer - .call(ViewOnOSM(context).entityID(entityID)); + .call(uiViewOnOSM(context).entityID(entityID)); function showList(preset) { @@ -107,5 +108,6 @@ export function Inspector(context) { return inspector; }; + return inspector; } diff --git a/modules/ui/intro/area.js b/modules/ui/intro/area.js index 048528e74..3670bcb00 100644 --- a/modules/ui/intro/area.js +++ b/modules/ui/intro/area.js @@ -1,11 +1,11 @@ import * as d3 from 'd3'; -import { rebind } from '../../util/rebind'; -import { bindOnce } from '../../util/bind_once'; import { t } from '../../util/locale'; +import { utilRebind } from '../../util/rebind'; +import { utilBindOnce } from '../../util/bind_once'; import { icon, pad } from './helper'; -export function area(context, reveal) { +export function uiIntroArea(context, reveal) { var dispatch = d3.dispatch('done'), timeout; @@ -75,7 +75,7 @@ export function area(context, reveal) { var first = d3.select('.preset-list-item:first-child'); if (first.classed('preset-leisure-playground')) { reveal(first.select('.preset-list-button').node(), t('intro.areas.choose')); - bindOnce(context.history(), 'change.intro', selectedPreset); + utilBindOnce(context.history(), 'change.intro', selectedPreset); d3.select('.preset-search-input').on('keyup.intro', null); } } @@ -101,5 +101,5 @@ export function area(context, reveal) { }; - return rebind(step, dispatch, 'on'); + return utilRebind(step, dispatch, 'on'); } diff --git a/modules/ui/intro/index.js b/modules/ui/intro/index.js index ae26d0bfa..babe9d5c0 100644 --- a/modules/ui/intro/index.js +++ b/modules/ui/intro/index.js @@ -1 +1 @@ -export { intro } from './intro'; +export { uiIntro } from './intro'; diff --git a/modules/ui/intro/intro.js b/modules/ui/intro/intro.js index db5d078f5..b1c8eb4e9 100644 --- a/modules/ui/intro/intro.js +++ b/modules/ui/intro/intro.js @@ -1,26 +1,27 @@ import * as d3 from 'd3'; import { t } from '../../util/locale'; -import { Entity, Graph } from '../../core/index'; -import { Browse } from '../../modes/index'; +import { coreEntity, coreGraph } from '../../core/index'; +import { modeBrowse } from '../../modes/index'; import { d3curtain } from '../../util/curtain'; import { default as introGraphRaw } from '../../../data/intro_graph.json'; -import { navigation } from './navigation'; -import { point } from './point'; -import { area } from './area'; -import { line } from './line'; -import { startEditing } from './start_editing'; +import { uiIntroNavigation } from './navigation'; +import { uiIntroPoint } from './point'; +import { uiIntroArea } from './area'; +import { uiIntroLine } from './line'; +import { uiIntroStartEditing } from './start_editing'; + var sampleIntros = { - navigation: navigation, - point: point, - area: area, - line: line, - startEditing: startEditing + navigation: uiIntroNavigation, + point: uiIntroPoint, + area: uiIntroArea, + line: uiIntroLine, + startEditing: uiIntroStartEditing }; -export function intro(context) { +export function uiIntro(context) { var step; function localizedName(id) { @@ -75,7 +76,7 @@ export function intro(context) { var introGraph = {}; for (var key in introGraphRaw) { - introGraph[key] = Entity(introGraphRaw[key]); + introGraph[key] = coreEntity(introGraphRaw[key]); var name = localizedName(key); if (name) { introGraph[key].tags.name = name; @@ -84,7 +85,7 @@ export function intro(context) { function intro(selection) { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); // Save current map state var history = context.history().toJSON(), @@ -103,7 +104,7 @@ export function intro(context) { context.connection().toggle(false).flush(); context.history().reset(); - context.history().merge(d3.values(Graph().load(introGraph).entities)); + context.history().merge(d3.values(coreGraph().load(introGraph).entities)); context.background().bing(); d3.selectAll('#map .layer-background').style('opacity', 1); @@ -176,7 +177,7 @@ export function intro(context) { function enter(newStep) { if (step) { step.exit(); } - context.enter(Browse(context)); + context.enter(modeBrowse(context)); step = newStep; step.enter(); diff --git a/modules/ui/intro/line.js b/modules/ui/intro/line.js index fed0e8a07..f4cbc9946 100644 --- a/modules/ui/intro/line.js +++ b/modules/ui/intro/line.js @@ -1,13 +1,13 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../../util/rebind'; -import { bindOnce } from '../../util/bind_once'; import { t } from '../../util/locale'; +import { actionDeleteMultiple } from '../../actions/index'; +import { utilRebind } from '../../util/rebind'; +import { utilBindOnce } from '../../util/bind_once'; import { icon, pad } from './helper'; -import { DeleteMultiple } from '../../actions/index'; -export function line(context, reveal) { +export function uiIntroLine(context, reveal) { var dispatch = d3.dispatch('done'), timeouts = []; @@ -83,7 +83,7 @@ export function line(context, reveal) { d3.select(window).on('mousedown.intro', eventCancel, true); timeout(function() { - context.replace(DeleteMultiple(ids)); + context.replace(actionDeleteMultiple(ids)); step.exit(); step.enter(); }, 3000); @@ -125,7 +125,7 @@ export function line(context, reveal) { d3.select('#curtain').style('pointer-events', 'none'); var road = d3.select('.preset-category-road .preset-list-button'); reveal(road.node(), t('intro.lines.road')); - bindOnce(road, 'click.intro', roadCategory); + utilBindOnce(road, 'click.intro', roadCategory); }, 500); } @@ -134,9 +134,9 @@ export function line(context, reveal) { timeout(function() { var grid = d3.select('.subgrid'); reveal(grid.node(), t('intro.lines.residential')); - bindOnce(grid.selectAll(':not(.preset-highway-residential) .preset-list-button'), + utilBindOnce(grid.selectAll(':not(.preset-highway-residential) .preset-list-button'), 'click.intro', retryPreset); - bindOnce(grid.selectAll('.preset-highway-residential .preset-list-button'), + utilBindOnce(grid.selectAll('.preset-highway-residential .preset-list-button'), 'click.intro', roadDetails); }, 500); } @@ -147,7 +147,7 @@ export function line(context, reveal) { timeout(function() { var preset = d3.select('.entity-editor-pane .preset-list-button'); reveal(preset.node(), t('intro.lines.wrong_preset')); - bindOnce(preset, 'click.intro', presetCategory); + utilBindOnce(preset, 'click.intro', presetCategory); }, 500); } @@ -172,5 +172,5 @@ export function line(context, reveal) { context.history().on('change.intro', null); }; - return rebind(step, dispatch, 'on'); + return utilRebind(step, dispatch, 'on'); } diff --git a/modules/ui/intro/navigation.js b/modules/ui/intro/navigation.js index 360fcb5d2..5883ce445 100644 --- a/modules/ui/intro/navigation.js +++ b/modules/ui/intro/navigation.js @@ -1,11 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../../util/rebind'; import { t } from '../../util/locale'; +import { utilRebind } from '../../util/rebind'; import { icon, pointBox } from './helper'; -export function navigation(context, reveal) { +export function uiIntroNavigation(context, reveal) { var dispatch = d3.dispatch('done'), timeouts = []; @@ -129,5 +129,5 @@ export function navigation(context, reveal) { }; - return rebind(step, dispatch, 'on'); + return utilRebind(step, dispatch, 'on'); } diff --git a/modules/ui/intro/point.js b/modules/ui/intro/point.js index 05afbf724..14f357c11 100644 --- a/modules/ui/intro/point.js +++ b/modules/ui/intro/point.js @@ -1,11 +1,11 @@ import * as d3 from 'd3'; -import { bindOnce } from '../../util/bind_once'; -import { rebind } from '../../util/rebind'; import { t } from '../../util/locale'; +import { utilBindOnce } from '../../util/bind_once'; +import { utilRebind } from '../../util/rebind'; import { icon, pad } from './helper'; -export function point(context, reveal) { +export function uiIntroPoint(context, reveal) { var dispatch = d3.dispatch('done'), timeouts = []; @@ -67,7 +67,7 @@ export function point(context, reveal) { var first = d3.select('.preset-list-item:first-child'); if (first.classed('preset-amenity-cafe')) { reveal(first.select('.preset-list-button').node(), t('intro.points.choose')); - bindOnce(context.history(), 'change.intro', selectedPreset); + utilBindOnce(context.history(), 'change.intro', selectedPreset); d3.select('.preset-search-input') .on('keydown.intro', eventCancel, true) .on('keyup.intro', null); @@ -171,5 +171,5 @@ export function point(context, reveal) { .on('keydown.intro', null); }; - return rebind(step, dispatch, 'on'); + return utilRebind(step, dispatch, 'on'); } diff --git a/modules/ui/intro/start_editing.js b/modules/ui/intro/start_editing.js index fd282857e..228e5923b 100644 --- a/modules/ui/intro/start_editing.js +++ b/modules/ui/intro/start_editing.js @@ -1,11 +1,11 @@ import * as d3 from 'd3'; -import { rebind } from '../../util/rebind'; import { t } from '../../util/locale'; import { icon } from './helper'; -import { modal } from '../modal'; +import { uiModal } from '../modal'; +import { utilRebind } from '../../util/rebind'; -export function startEditing(context, reveal) { +export function uiIntroStartEditing(context, reveal) { var dispatch = d3.dispatch('done', 'startEditing'), modalSelection, timeouts = []; @@ -33,7 +33,7 @@ export function startEditing(context, reveal) { }, 10000); timeout(function() { - modalSelection = modal(context.container()); + modalSelection = uiModal(context.container()); modalSelection.select('.modal') .attr('class', 'modal-splash modal col6'); @@ -67,5 +67,5 @@ export function startEditing(context, reveal) { }; - return rebind(step, dispatch, 'on'); + return utilRebind(step, dispatch, 'on'); } diff --git a/modules/ui/lasso.js b/modules/ui/lasso.js index 11bf90173..085ef321f 100644 --- a/modules/ui/lasso.js +++ b/modules/ui/lasso.js @@ -1,26 +1,30 @@ import * as d3 from 'd3'; -import { Extent } from '../geo/index'; -import { Toggle } from './toggle'; +import { geoExtent } from '../geo/index'; +import { uiToggle } from './toggle'; -export function Lasso(context) { + +export function uiLasso(context) { var group, polygon; lasso.coordinates = []; function lasso(selection) { + context.container() + .classed('lasso', true); - context.container().classed('lasso', true); - - group = selection.append('g') + group = selection + .append('g') .attr('class', 'lasso hide'); - polygon = group.append('path') + polygon = group + .append('path') .attr('class', 'lasso-path'); - group.call(Toggle(true)); - + group + .call(uiToggle(true)); } + function draw() { if (polygon) { polygon.data([lasso.coordinates]) @@ -28,12 +32,14 @@ export function Lasso(context) { } } + lasso.extent = function () { return lasso.coordinates.reduce(function(extent, point) { return extent.extend(Extent(point)); - }, Extent()); + }, geoExtent()); }; + lasso.p = function(_) { if (!arguments.length) return lasso; lasso.coordinates.push(_); @@ -41,14 +47,16 @@ export function Lasso(context) { return lasso; }; + lasso.close = function() { if (group) { - group.call(Toggle(false, function() { + group.call(uiToggle(false, function() { d3.select(this).remove(); })); } context.container().classed('lasso', false); }; + return lasso; } diff --git a/modules/ui/loading.js b/modules/ui/loading.js index 54f37a093..ba91d8692 100644 --- a/modules/ui/loading.js +++ b/modules/ui/loading.js @@ -1,23 +1,26 @@ -import { modal } from './modal'; +import { uiModal } from './modal'; -export function Loading(context) { +export function uiLoading(context) { var message = '', blocking = false, modalSelection; + var loading = function(selection) { - modalSelection = modal(selection, blocking); + modalSelection = uiModal(selection, blocking); var loadertext = modalSelection.select('.content') .classed('loading-modal', true) .append('div') .attr('class', 'modal-section fillL'); - loadertext.append('img') + loadertext + .append('img') .attr('class', 'loader') .attr('src', context.imagePath('loader-white.gif')); - loadertext.append('h3') + loadertext + .append('h3') .text(message); modalSelection.select('button.close') @@ -26,21 +29,25 @@ export function Loading(context) { return loading; }; + loading.message = function(_) { if (!arguments.length) return message; message = _; return loading; }; + loading.blocking = function(_) { if (!arguments.length) return blocking; blocking = _; return loading; }; + loading.close = function() { modalSelection.remove(); }; + return loading; } diff --git a/modules/ui/map_data.js b/modules/ui/map_data.js index 51ddc30a4..f5efc3192 100644 --- a/modules/ui/map_data.js +++ b/modules/ui/map_data.js @@ -2,11 +2,12 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; +import { svgIcon } from '../svg/index'; +import { uiTooltipHtml } from './tooltipHtml'; import { tooltip } from '../util/tooltip'; -import { Icon } from '../svg/index'; -import { tooltipHtml } from './tooltipHtml'; -export function MapData(context) { + +export function uiMapData(context) { var key = 'F', features = context.features().keys(), layers = context.layers(), @@ -21,19 +22,23 @@ export function MapData(context) { return context.features().enabled(d); } + function autoHiddenFeature(d) { return context.features().autoHidden(d); } + function clickFeature(d) { context.features().toggle(d); update(); } + function showsFill(d) { return fillSelected === d; } + function setFill(d) { _.each(fills, function(opt) { context.surface().classed('fill-' + opt, Boolean(opt === d)); @@ -47,6 +52,7 @@ export function MapData(context) { update(); } + function showsLayer(which) { var layer = layers.layer(which); if (layer) { @@ -55,6 +61,7 @@ export function MapData(context) { return false; } + function setLayer(which, enabled) { var layer = layers.layer(which); if (layer) { @@ -63,14 +70,17 @@ export function MapData(context) { } } + function toggleLayer(which) { setLayer(which, !showsLayer(which)); } + function clickGpx() { toggleLayer('gpx'); } + function clickMapillaryImages() { toggleLayer('mapillary-images'); if (!showsLayer('mapillary-images')) { @@ -78,6 +88,7 @@ export function MapData(context) { } } + function clickMapillarySigns() { toggleLayer('mapillary-signs'); } @@ -95,30 +106,36 @@ export function MapData(context) { .selectAll('.layer-list-mapillary') .data([0]); - // Enter - mapillaryList - .enter() + mapillaryList = mapillaryList.enter() .append('ul') - .attr('class', 'layer-list layer-list-mapillary'); + .attr('class', 'layer-list layer-list-mapillary') + .merge(mapillaryList); + var mapillaryImageLayerItem = mapillaryList .selectAll('.list-item-mapillary-images') .data(supportsMapillaryImages ? [0] : []); + mapillaryImageLayerItem.exit() + .remove(); + var enterImages = mapillaryImageLayerItem.enter() .append('li') .attr('class', 'list-item-mapillary-images'); - var labelImages = enterImages.append('label') + var labelImages = enterImages + .append('label') .call(tooltip() .title(t('mapillary_images.tooltip')) .placement('top')); - labelImages.append('input') + labelImages + .append('input') .attr('type', 'checkbox') .on('change', clickMapillaryImages); - labelImages.append('span') + labelImages + .append('span') .text(t('mapillary_images.title')); @@ -126,28 +143,42 @@ export function MapData(context) { .selectAll('.list-item-mapillary-signs') .data(supportsMapillarySigns ? [0] : []); + mapillarySignLayerItem.exit() + .remove(); + var enterSigns = mapillarySignLayerItem.enter() .append('li') .attr('class', 'list-item-mapillary-signs'); - var labelSigns = enterSigns.append('label') + var labelSigns = enterSigns + .append('label') .call(tooltip() .title(t('mapillary_signs.tooltip')) .placement('top')); - labelSigns.append('input') + labelSigns + .append('input') .attr('type', 'checkbox') .on('change', clickMapillarySigns); - labelSigns.append('span') + labelSigns + .append('span') .text(t('mapillary_signs.title')); - // Update + + // Updates + mapillaryImageLayerItem = mapillaryImageLayerItem + .merge(enterImages); + mapillaryImageLayerItem .classed('active', showsMapillaryImages) .selectAll('input') .property('checked', showsMapillaryImages); + + mapillarySignLayerItem = mapillarySignLayerItem + .merge(enterSigns); + mapillarySignLayerItem .classed('active', showsMapillarySigns) .selectAll('input') @@ -157,12 +188,6 @@ export function MapData(context) { mapillarySignLayerItem .selectAll('label') .classed('deemphasize', !showsMapillaryImages); - - // Exit - mapillaryImageLayerItem.exit() - .remove(); - mapillarySignLayerItem.exit() - .remove(); } @@ -175,6 +200,10 @@ export function MapData(context) { .selectAll('.layer-list-gpx') .data(gpx ? [0] : []); + // Exit + gpxLayerItem.exit() + .remove(); + // Enter var enter = gpxLayerItem.enter() .append('ul') @@ -182,7 +211,8 @@ export function MapData(context) { .append('li') .classed('list-item-gpx', true); - enter.append('button') + enter + .append('button') .attr('class', 'list-item-gpx-extent') .call(tooltip() .title(t('gpx.zoom')) @@ -192,9 +222,10 @@ export function MapData(context) { d3.event.stopPropagation(); gpx.fitZoom(); }) - .call(Icon('#icon-search')); + .call(svgIcon('#icon-search')); - enter.append('button') + enter + .append('button') .attr('class', 'list-item-gpx-browse') .call(tooltip() .title(t('gpx.browse')) @@ -207,21 +238,26 @@ export function MapData(context) { }) .node().click(); }) - .call(Icon('#icon-geolocate')); + .call(svgIcon('#icon-geolocate')); - var labelGpx = enter.append('label') - .call(tooltip() - .title(t('gpx.drag_drop')) - .placement('top')); + var labelGpx = enter + .append('label') + .call(tooltip().title(t('gpx.drag_drop')).placement('top')); - labelGpx.append('input') + labelGpx + .append('input') .attr('type', 'checkbox') .on('change', clickGpx); - labelGpx.append('span') + labelGpx + .append('span') .text(t('gpx.local_layer')); + // Update + gpxLayerItem = gpxLayerItem + .merge(enter); + gpxLayerItem .classed('active', showsGpx) .selectAll('input') @@ -231,10 +267,6 @@ export function MapData(context) { gpxLayerItem .selectAll('label') .classed('deemphasize', !hasGpx); - - // Exit - gpxLayerItem.exit() - .remove(); } @@ -242,6 +274,10 @@ export function MapData(context) { var items = selection.selectAll('li') .data(data); + // Exit + items.exit() + .remove(); + // Enter var enter = items.enter() .append('li') @@ -255,22 +291,28 @@ export function MapData(context) { if (name === 'feature' && autoHiddenFeature(d)) { tip += '
' + t('map_data.autohidden') + '
'; } - return tooltipHtml(tip, key); + return uiTooltipHtml(tip, key); }) .placement('top') ); - var label = enter.append('label'); + var label = enter + .append('label'); - label.append('input') + label + .append('input') .attr('type', type) .attr('name', name) .on('change', change); - label.append('span') + label + .append('span') .text(function(d) { return t(name + '.' + d + '.description'); }); // Update + items = items + .merge(enter); + items .classed('active', active) .selectAll('input') @@ -278,10 +320,6 @@ export function MapData(context) { .property('indeterminate', function(d) { return (name === 'feature' && autoHiddenFeature(d)); }); - - // Exit - items.exit() - .remove(); } @@ -290,20 +328,22 @@ export function MapData(context) { dataLayerContainer.call(drawGpxItem); fillList.call(drawList, fills, 'radio', 'area_fill', setFill, showsFill); - featureList.call(drawList, features, 'checkbox', 'feature', clickFeature, showsFeature); } + function hidePanel() { setVisible(false); } + function togglePanel() { if (d3.event) d3.event.preventDefault(); tooltipBehavior.hide(button); setVisible(!button.classed('active')); } + function toggleWireframe() { if (d3.event) { d3.event.preventDefault(); @@ -313,6 +353,7 @@ export function MapData(context) { context.map().pan([0,0]); // trigger a redraw } + function setVisible(show) { if (show !== shown) { button.classed('active', show); @@ -343,25 +384,29 @@ export function MapData(context) { } - var content = selection.append('div') + var content = selection + .append('div') .attr('class', 'fillL map-overlay col3 content hide'), tooltipBehavior = tooltip() .placement('left') .html(true) - .title(tooltipHtml(t('map_data.description'), key)), - button = selection.append('button') + .title(uiTooltipHtml(t('map_data.description'), key)), + button = selection + .append('button') .attr('tabindex', -1) .on('click', togglePanel) - .call(Icon('#icon-data', 'light')) + .call(svgIcon('#icon-data', 'light')) .call(tooltipBehavior), shown = false; - content.append('h4') + content + .append('h4') .text(t('map_data.title')); // data layers - content.append('a') + content + .append('a') .text(t('map_data.data_layers')) .attr('href', '#') .classed('hide-toggle', true) @@ -373,13 +418,15 @@ export function MapData(context) { d3.event.preventDefault(); }); - var dataLayerContainer = content.append('div') + var dataLayerContainer = content + .append('div') .attr('class', 'data-data-layers') .style('display', 'block'); // area fills - content.append('a') + content + .append('a') .text(t('map_data.fill_area')) .attr('href', '#') .classed('hide-toggle', true) @@ -391,16 +438,19 @@ export function MapData(context) { d3.event.preventDefault(); }); - var fillContainer = content.append('div') + var fillContainer = content + .append('div') .attr('class', 'data-area-fills') .style('display', 'none'); - var fillList = fillContainer.append('ul') + var fillList = fillContainer + .append('ul') .attr('class', 'layer-list layer-fill-list'); // feature filters - content.append('a') + content + .append('a') .text(t('map_data.map_features')) .attr('href', '#') .classed('hide-toggle', true) @@ -412,11 +462,13 @@ export function MapData(context) { d3.event.preventDefault(); }); - var featureContainer = content.append('div') + var featureContainer = content + .append('div') .attr('class', 'data-feature-filters') .style('display', 'none'); - var featureList = featureContainer.append('ul') + var featureList = featureContainer + .append('ul') .attr('class', 'layer-list layer-feature-list'); @@ -438,5 +490,6 @@ export function MapData(context) { context.container().on('mousedown.map_data-outside', hidePanel); } + return map_data; } diff --git a/modules/ui/map_in_map.js b/modules/ui/map_in_map.js index fc93dca5d..3078a5df1 100644 --- a/modules/ui/map_in_map.js +++ b/modules/ui/map_in_map.js @@ -1,10 +1,11 @@ import * as d3 from 'd3'; import { d3keybinding } from '../lib/d3.keybinding.js'; -import { Debug, Gpx } from '../svg/index'; -import { RawMercator } from '../geo/index'; -import { TileLayer } from '../renderer/index'; -import { setTransform } from '../util/index'; -import { getDimensions } from '../util/dimensions'; +import { svgDebug, svgGpx } from '../svg/index'; +import { geoRawMercator } from '../geo/index'; +import { rendererTileLayer } from '../renderer/index'; +import { utilSetTransform } from '../util/index'; +import { utilGetDimensions } from '../util/dimensions'; + var TAU = 2 * Math.PI; function ztok(z) { return 256 * Math.pow(2, z) / TAU; } @@ -13,15 +14,16 @@ function vecSub(a, b) { return [ a[0] - b[0], a[1] - b[1] ]; } function vecScale(a, b) { return [ a[0] * b, a[1] * b ]; } -export function MapInMap(context) { +export function uiMapInMap(context) { var key = '/'; + function map_in_map(selection) { - var backgroundLayer = TileLayer(context), + var backgroundLayer = rendererTileLayer(context), overlayLayers = {}, - projection = RawMercator(), - gpxLayer = Gpx(projection, context).showLabels(false), - debugLayer = Debug(projection, context), + projection = geoRawMercator(), + gpxLayer = svgGpx(projection, context).showLabels(false), + debugLayer = svgDebug(projection, context), zoom = d3.zoom() .scaleExtent([ztok(0.5), ztok(24)]) .on('start', zoomStarted) @@ -69,7 +71,7 @@ export function MapInMap(context) { tX, tY, scale; if (gesture === 'zoom') { - var dMini = getDimensions(wrap), + var dMini = utilGetDimensions(wrap), cMini = vecScale(dMini, 0.5); scale = k / tMini.k; tX = (cMini[0] / scale - cMini[0]) * scale; @@ -81,8 +83,8 @@ export function MapInMap(context) { tY = y - tMini.y; } - setTransform(tiles, tX, tY, scale); - setTransform(viewport, 0, 0, scale); + utilSetTransform(tiles, tX, tY, scale); + utilSetTransform(viewport, 0, 0, scale); isTransformed = true; tCurr = d3.zoomIdentity.translate(x, y).scale(k); @@ -101,7 +103,7 @@ export function MapInMap(context) { updateProjection(); gesture = null; - var dMini = getDimensions(wrap), + var dMini = utilGetDimensions(wrap), cMini = vecScale(dMini, 0.5); context.map().center(projection.invert(cMini)); // recenter main map.. } @@ -109,7 +111,7 @@ export function MapInMap(context) { function updateProjection() { var loc = context.map().center(), - dMini = getDimensions(wrap), + dMini = utilGetDimensions(wrap), cMini = vecScale(dMini, 0.5), tMain = context.projection.transform(), zMain = ktoz(tMain.k), @@ -132,8 +134,8 @@ export function MapInMap(context) { tCurr = projection.transform(); if (isTransformed) { - setTransform(tiles, 0, 0); - setTransform(viewport, 0, 0); + utilSetTransform(tiles, 0, 0); + utilSetTransform(viewport, 0, 0); isTransformed = false; } @@ -152,7 +154,7 @@ export function MapInMap(context) { updateProjection(); - var dMini = getDimensions(wrap), + var dMini = utilGetDimensions(wrap), zMini = ktoz(projection.scale()); // setup tile container @@ -300,7 +302,7 @@ export function MapInMap(context) { } - MapInMap.toggle = toggle; + uiMapInMap.toggle = toggle; wrap = selection.selectAll('.map-in-map') .data([0]); diff --git a/modules/ui/modal.js b/modules/ui/modal.js index aa3d9315c..8c98af1f9 100644 --- a/modules/ui/modal.js +++ b/modules/ui/modal.js @@ -1,8 +1,9 @@ import * as d3 from 'd3'; import { d3keybinding } from '../lib/d3.keybinding.js'; -import { Icon } from '../svg/index'; +import { svgIcon } from '../svg/index'; -export function modal(selection, blocking) { + +export function uiModal(selection, blocking) { var keybinding = d3keybinding('modal'); var previous = selection.select('div.modal'); var animate = previous.empty(); @@ -23,6 +24,7 @@ export function modal(selection, blocking) { .duration(200) .style('opacity',0) .remove(); + modal .transition() .duration(200) @@ -32,7 +34,8 @@ export function modal(selection, blocking) { }; - var modal = shaded.append('div') + var modal = shaded + .append('div') .attr('class', 'modal fillL col6'); if (!blocking) { @@ -45,16 +48,18 @@ export function modal(selection, blocking) { modal.append('button') .attr('class', 'close') .on('click', shaded.close) - .call(Icon('#icon-close')); + .call(svgIcon('#icon-close')); keybinding .on('⌫', shaded.close) .on('⎋', shaded.close); - d3.select(document).call(keybinding); + d3.select(document) + .call(keybinding); } - modal.append('div') + modal + .append('div') .attr('class', 'content'); if (animate) { @@ -63,5 +68,6 @@ export function modal(selection, blocking) { shaded.style('opacity', 1); } + return shaded; } diff --git a/modules/ui/modes.js b/modules/ui/modes.js index 9a9bda5e8..7c70b09cc 100644 --- a/modules/ui/modes.js +++ b/modules/ui/modes.js @@ -1,41 +1,53 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; -import { tooltip } from '../util/tooltip'; -import { AddArea, AddLine, AddPoint, Browse } from '../modes/index'; -import { Icon } from '../svg/index'; -import { tooltipHtml } from './tooltipHtml'; +import { + modeAddArea, + modeAddLine, + modeAddPoint, + modeBrowse +} from '../modes/index'; -export function Modes(context) { +import { svgIcon } from '../svg/index'; +import { tooltip } from '../util/tooltip'; +import { uiTooltipHtml } from './tooltipHtml'; + + +export function uiModes(context) { var modes = [ - AddPoint(context), - AddLine(context), - AddArea(context)]; + modeAddPoint(context), + modeAddLine(context), + modeAddArea(context) + ]; + function editable() { return context.editable() && context.mode().id !== 'save'; } + return function(selection) { var buttons = selection.selectAll('button.add-button') .data(modes); - buttons = buttons.enter().append('button') - .attr('tabindex', -1) - .attr('class', function(mode) { return mode.id + ' add-button col4'; }) - .on('click.mode-buttons', function(mode) { - if (mode.id === context.mode().id) { - context.enter(Browse(context)); - } else { - context.enter(mode); - } - }) - .call(tooltip() - .placement('bottom') - .html(true) - .title(function(mode) { - return tooltipHtml(mode.description, mode.key); - })); + buttons = buttons.enter() + .append('button') + .attr('tabindex', -1) + .attr('class', function(mode) { return mode.id + ' add-button col4'; }) + .on('click.mode-buttons', function(mode) { + if (mode.id === context.mode().id) { + context.enter(modeBrowse(context)); + } else { + context.enter(mode); + } + }) + .call(tooltip() + .placement('bottom') + .html(true) + .title(function(mode) { + return uiTooltipHtml(mode.description, mode.key); + }) + ); context.map() .on('move.modes', _.debounce(update, 500)); @@ -43,37 +55,46 @@ export function Modes(context) { context .on('enter.modes', update); - buttons.each(function(d) { - d3.select(this) - .call(Icon('#icon-' + d.button, 'pre-text')); - }); + buttons + .each(function(d) { + d3.select(this) + .call(svgIcon('#icon-' + d.button, 'pre-text')); + }); - buttons.append('span') + buttons + .append('span') .attr('class', 'label') .text(function(mode) { return mode.title; }); - context.on('enter.editor', function(entered) { - buttons.classed('active', function(mode) { return entered.button === mode.button; }); - context.container() - .classed('mode-' + entered.id, true); - }); + context + .on('enter.editor', function(entered) { + selection.selectAll('button.add-button') + .classed('active', function(mode) { return entered.button === mode.button; }); + context.container() + .classed('mode-' + entered.id, true); + }); - context.on('exit.editor', function(exited) { - context.container() - .classed('mode-' + exited.id, false); - }); + context + .on('exit.editor', function(exited) { + context.container() + .classed('mode-' + exited.id, false); + }); var keybinding = d3keybinding('mode-buttons'); modes.forEach(function(m) { - keybinding.on(m.key, function() { if (editable()) context.enter(m); }); + keybinding.on(m.key, function() { + if (editable()) context.enter(m); + }); }); d3.select(document) .call(keybinding); + function update() { - buttons.property('disabled', !editable()); + selection.selectAll('button.add-button') + .property('disabled', !editable()); } }; } diff --git a/modules/ui/notice.js b/modules/ui/notice.js index 434abd829..db8ff9970 100644 --- a/modules/ui/notice.js +++ b/modules/ui/notice.js @@ -1,22 +1,27 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { Icon } from '../svg/index'; +import { svgIcon } from '../svg/index'; + + +export function uiNotice(context) { -export function Notice(context) { return function(selection) { - var div = selection.append('div') + var div = selection + .append('div') .attr('class', 'notice'); - var button = div.append('button') + var button = div + .append('button') .attr('class', 'zoom-to notice') .on('click', function() { context.map().zoom(context.minEditableZoom()); }); button - .call(Icon('#icon-plus', 'pre-text')) + .call(svgIcon('#icon-plus', 'pre-text')) .append('span') .attr('class', 'label') .text(t('zoom_in_edit')); + function disableTooHigh() { div.style('display', context.editable() ? 'none' : 'block'); } diff --git a/modules/ui/preset.js b/modules/ui/preset.js index d5bddceac..c9942b611 100644 --- a/modules/ui/preset.js +++ b/modules/ui/preset.js @@ -1,17 +1,17 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { rebind } from '../util/rebind'; -import { getSetValue } from '../util/get_set_value'; import { d3combobox } from '../lib/d3.combobox.js'; import { t } from '../util/locale'; -import { Browse } from '../modes/index'; -import { Disclosure } from './disclosure'; -import { Icon } from '../svg/index'; -import { TagReference } from './tag_reference'; -import { fields } from './fields/index'; +import { modeBrowse } from '../modes/index'; +import { svgIcon } from '../svg/index'; +import { uiDisclosure } from './disclosure'; +import { uiFields } from './fields/index'; +import { uiTagReference } from './tag_reference'; +import { utilRebind } from '../util/rebind'; +import { utilGetSetValue } from '../util/get_set_value'; -export function preset(context) { +export function uiPreset(context) { var dispatch = d3.dispatch('change'), state, fieldsArr, @@ -20,6 +20,7 @@ export function preset(context) { id; + // Field Constructor function UIField(field, entity, show) { field = _.clone(field); @@ -78,11 +79,12 @@ export function preset(context) { function presets(selection) { - selection.call(Disclosure() + selection.call(uiDisclosure() .title(t('inspector.all_fields')) .expanded(context.storage('preset_fields.expanded') !== 'false') .on('toggled', toggled) - .content(content)); + .content(content) + ); function toggled(expanded) { context.storage('preset_fields.expanded', expanded); @@ -153,12 +155,12 @@ export function preset(context) { wrap.append('button') .attr('class', 'remove-icon') .attr('tabindex', -1) - .call(Icon('#operation-delete')); + .call(svgIcon('#operation-delete')); wrap.append('button') .attr('class', 'modified-icon') .attr('tabindex', -1) - .call(Icon('#icon-undo')); + .call(svgIcon('#icon-undo')); // Update @@ -176,7 +178,7 @@ export function preset(context) { .classed('modified', function(field) { return field.modified(); }) .classed('present', function(field) { return field.present(); }) .each(function(field) { - var reference = TagReference(field.reference || {key: field.key}, context); + var reference = uiTagReference(field.reference || { key: field.key }, context); if (state === 'hover') { reference.showing(false); @@ -188,7 +190,7 @@ export function preset(context) { .on('keydown', function() { // if user presses enter, and combobox is not active, accept edits.. if (d3.event.keyCode === 13 && d3.select('.combobox').empty()) { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } }) .call(reference.body) @@ -234,7 +236,7 @@ export function preset(context) { .merge(input); input - .call(getSetValue, '') + .call(utilGetSetValue, '') .attr('placeholder', function() { var placeholder = []; for (var field in notShown) { @@ -254,12 +256,14 @@ export function preset(context) { field.input.focus(); } + function revert(field) { d3.event.stopPropagation(); d3.event.preventDefault(); dispatch.call('change', field, field.revert()); } + function remove(field) { d3.event.stopPropagation(); d3.event.preventDefault(); @@ -301,5 +305,5 @@ export function preset(context) { }; - return rebind(presets, dispatch, 'on'); + return utilRebind(presets, dispatch, 'on'); } diff --git a/modules/ui/preset_icon.js b/modules/ui/preset_icon.js index 7750dde89..737484b02 100644 --- a/modules/ui/preset_icon.js +++ b/modules/ui/preset_icon.js @@ -1,22 +1,24 @@ import * as d3 from 'd3'; -import { functor } from '../util/index'; -import { Icon } from '../svg/index'; -import { featureIcons } from '../../data/index'; +import { dataFeatureIcons } from '../../data/index'; +import { svgIcon } from '../svg/index'; +import { utilFunctor } from '../util/index'; -export function PresetIcon() { +export function uiPresetIcon() { var preset, geometry; + function presetIcon(selection) { selection.each(render); } + function render() { var selection = d3.select(this), p = preset.apply(this, arguments), geom = geometry.apply(this, arguments), picon = p.icon || (geom === 'line' ? 'other-line' : 'marker-stroked'), - isMaki = featureIcons.hasOwnProperty(picon + '-24'); + isMaki = dataFeatureIcons.hasOwnProperty(picon + '-24'); if (picon === 'dentist') { isMaki = true; // workaround for dentist icon missing in `maki-sprite.json` @@ -50,7 +52,7 @@ export function PresetIcon() { frame = frame.enter() .append('div') - .call(Icon('#preset-icon-frame')) + .call(svgIcon('#preset-icon-frame')) .merge(frame); frame @@ -65,7 +67,7 @@ export function PresetIcon() { icon = icon.enter() .append('div') .attr('class', 'preset-icon') - .call(Icon('')) + .call(svgIcon('')) .merge(icon); icon @@ -83,14 +85,14 @@ export function PresetIcon() { presetIcon.preset = function(_) { if (!arguments.length) return preset; - preset = functor(_); + preset = utilFunctor(_); return presetIcon; }; presetIcon.geometry = function(_) { if (!arguments.length) return geometry; - geometry = functor(_); + geometry = utilFunctor(_); return presetIcon; }; diff --git a/modules/ui/preset_list.js b/modules/ui/preset_list.js index 946fe5856..cf879143e 100644 --- a/modules/ui/preset_list.js +++ b/modules/ui/preset_list.js @@ -1,16 +1,16 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; +import { utilRebind } from '../util/rebind'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; -import { Browse } from '../modes/index'; -import { ChangePreset } from '../actions/index'; -import { Delete } from '../operations/index'; -import { Icon } from '../svg/index'; -import { PresetIcon } from './preset_icon'; -import { TagReference } from './tag_reference'; +import { actionChangePreset } from '../actions/index'; +import { operationDelete } from '../operations/index'; +import { modeBrowse } from '../modes/index'; +import { svgIcon } from '../svg/index'; +import { uiPresetIcon } from './preset_icon'; +import { uiTagReference } from './tag_reference'; -export function PresetList(context) { +export function uiPresetList(context) { var dispatch = d3.dispatch('choose'), id, currentPreset, @@ -30,25 +30,29 @@ export function PresetList(context) { selection.html(''); - var messagewrap = selection.append('div') + var messagewrap = selection + .append('div') .attr('class', 'header fillL cf'); - var message = messagewrap.append('h3') + var message = messagewrap + .append('h3') .text(t('inspector.choose')); if (context.entity(id).isUsed(context.graph())) { - messagewrap.append('button') + messagewrap + .append('button') .attr('class', 'preset-choose') .on('click', function() { dispatch.call('choose', this, currentPreset); }) .append('span') .html('►'); } else { - messagewrap.append('button') + messagewrap + .append('button') .attr('class', 'close') .on('click', function() { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); }) - .call(Icon('#icon-close')); + .call(svgIcon('#icon-close')); } function keydown() { @@ -58,7 +62,7 @@ export function PresetList(context) { d3.event.keyCode === d3keybinding.keyCodes['⌦'])) { d3.event.preventDefault(); d3.event.stopPropagation(); - Delete([id], context)(); + operationDelete([id], context)(); } else if (search.property('value').length === 0 && (d3.event.ctrlKey || d3.event.metaKey) && d3.event.keyCode === d3keybinding.keyCodes.z) { @@ -94,10 +98,12 @@ export function PresetList(context) { } } - var searchWrap = selection.append('div') + var searchWrap = selection + .append('div') .attr('class', 'search-header'); - var search = searchWrap.append('input') + var search = searchWrap + .append('input') .attr('class', 'preset-search-input') .attr('placeholder', t('inspector.search')) .attr('type', 'search') @@ -106,16 +112,18 @@ export function PresetList(context) { .on('input', inputevent); searchWrap - .call(Icon('#icon-search', 'pre-text')); + .call(svgIcon('#icon-search', 'pre-text')); if (autofocus) { search.node().focus(); } - var listWrap = selection.append('div') + var listWrap = selection + .append('div') .attr('class', 'inspector-body'); - var list = listWrap.append('div') + var list = listWrap + .append('div') .attr('class', 'preset-list fillL cf') .call(drawList, context.presets().defaults(geometry, 36)); } @@ -155,7 +163,7 @@ export function PresetList(context) { wrap.append('button') .attr('class', 'preset-list-button') .classed('expanded', false) - .call(PresetIcon() + .call(uiPresetIcon() .geometry(context.geometry(id)) .preset(preset)) .on('click', function() { @@ -217,7 +225,7 @@ export function PresetList(context) { wrap.append('button') .attr('class', 'preset-list-button') - .call(PresetIcon() + .call(uiPresetIcon() .geometry(context.geometry(id)) .preset(preset)) .on('click', item.choose) @@ -233,8 +241,9 @@ export function PresetList(context) { context.presets().choose(preset); context.perform( - ChangePreset(id, currentPreset, preset), - t('operations.change_tags.annotation')); + actionChangePreset(id, currentPreset, preset), + t('operations.change_tags.annotation') + ); dispatch.call('choose', this, preset); }; @@ -245,7 +254,7 @@ export function PresetList(context) { }; item.preset = preset; - item.reference = TagReference(preset.reference(context.geometry(id)), context); + item.reference = uiTagReference(preset.reference(context.geometry(id)), context); return item; } @@ -272,5 +281,6 @@ export function PresetList(context) { return presetList; }; - return rebind(presetList, dispatch, 'on'); + + return utilRebind(presetList, dispatch, 'on'); } diff --git a/modules/ui/radial_menu.js b/modules/ui/radial_menu.js index 546b5fb19..8c62abb5b 100644 --- a/modules/ui/radial_menu.js +++ b/modules/ui/radial_menu.js @@ -1,32 +1,34 @@ import * as d3 from 'd3'; -import { roundCoords } from '../geo/index'; -import { tooltipHtml } from './tooltipHtml'; +import { geoRoundCoords } from '../geo/index'; +import { uiTooltipHtml } from './tooltipHtml'; -export function RadialMenu(context, operations) { + +export function uiRadialMenu(context, operations) { var menu, center = [0, 0], tooltip; + var radialMenu = function(selection) { - if (!operations.length) - return; + if (!operations.length) return; selection.node().parentNode.focus(); function click(operation) { d3.event.stopPropagation(); - if (operation.disabled()) - return; + if (operation.disabled()) return; operation(); radialMenu.close(); } - menu = selection.append('g') + menu = selection + .append('g') .attr('class', 'radial-menu') .attr('transform', 'translate(' + center + ')') .attr('opacity', 0); - menu.transition() + menu + .transition() .attr('opacity', 1); var r = 50, @@ -34,7 +36,8 @@ export function RadialMenu(context, operations) { a0 = -Math.PI / 4, a1 = a0 + (operations.length - 1) * a; - menu.append('path') + menu + .append('path') .attr('class', 'radial-menu-background') .attr('d', 'M' + r * Math.sin(a0) + ',' + r * Math.cos(a0) + @@ -51,19 +54,21 @@ export function RadialMenu(context, operations) { .attr('class', function(d) { return 'radial-menu-item radial-menu-item-' + d.id; }) .classed('disabled', function(d) { return d.disabled(); }) .attr('transform', function(d, i) { - return 'translate(' + roundCoords([ + return 'translate(' + geoRoundCoords([ r * Math.sin(a0 + i * a), r * Math.cos(a0 + i * a)]).join(',') + ')'; }); - button.append('circle') + button + .append('circle') .attr('r', 15) .on('click', click) .on('mousedown', mousedown) .on('mouseover', mouseover) .on('mouseout', mouseout); - button.append('use') + button + .append('use') .attr('transform', 'translate(-10,-10)') .attr('width', '20') .attr('height', '20') @@ -91,7 +96,7 @@ export function RadialMenu(context, operations) { .style('bottom', null) .style('right', null) .style('display', 'block') - .html(tooltipHtml(d.tooltip(), d.keys[0])); + .html(uiTooltipHtml(d.tooltip(), d.keys[0])); if (i === 0) { tooltip @@ -113,6 +118,7 @@ export function RadialMenu(context, operations) { } }; + radialMenu.close = function() { if (menu) { menu @@ -127,11 +133,13 @@ export function RadialMenu(context, operations) { } }; + radialMenu.center = function(_) { if (!arguments.length) return center; center = _; return radialMenu; }; + return radialMenu; } diff --git a/modules/ui/raw_member_editor.js b/modules/ui/raw_member_editor.js index 43df9c0ae..6dbfda20b 100644 --- a/modules/ui/raw_member_editor.js +++ b/modules/ui/raw_member_editor.js @@ -1,40 +1,42 @@ import * as d3 from 'd3'; import { d3combobox } from '../lib/d3.combobox.js'; import { t } from '../util/locale'; -import { Browse, Select } from '../modes/index'; -import { ChangeMember, DeleteMember } from '../actions/index'; -import { Disclosure } from './disclosure'; -import { Entity } from '../core/index'; -import { Icon } from '../svg/index'; -import { displayName } from '../util/index'; +import { actionChangeMember, actionDeleteMember } from '../actions/index'; +import { coreEntity } from '../core/index'; +import { modeBrowse, modeSelect } from '../modes/index'; +import { svgIcon } from '../svg/index'; +import { uiDisclosure } from './disclosure'; +import { utilDisplayName } from '../util/index'; -export function RawMemberEditor(context) { +export function uiRawMemberEditor(context) { var id; function selectMember(d) { d3.event.preventDefault(); - context.enter(Select(context, [d.id])); + context.enter(modeSelect(context, [d.id])); } function changeRole(d) { var role = d3.select(this).property('value'); - var member = {id: d.id, type: d.type, role: role}; + var member = { id: d.id, type: d.type, role: role }; context.perform( - ChangeMember(d.relation.id, member, d.index), - t('operations.change_role.annotation')); + actionChangeMember(d.relation.id, member, d.index), + t('operations.change_role.annotation') + ); } function deleteMember(d) { context.perform( - DeleteMember(d.relation.id, d.index), - t('operations.delete_member.annotation')); + actionDeleteMember(d.relation.id, d.index), + t('operations.delete_member.annotation') + ); if (!context.hasEntity(d.relation.id)) { - context.enter(Browse(context)); + context.enter(modeBrowse(context)); } } @@ -54,11 +56,13 @@ export function RawMemberEditor(context) { }); }); - selection.call(Disclosure() + selection.call(uiDisclosure() .title(t('inspector.all_members') + ' (' + memberships.length + ')') .expanded(true) .on('toggled', toggled) - .content(content)); + .content(content) + ); + function toggled(expanded) { if (expanded) { @@ -66,6 +70,7 @@ export function RawMemberEditor(context) { } } + function content(wrap) { var list = wrap.selectAll('.member-list') .data([0]); @@ -76,8 +81,8 @@ export function RawMemberEditor(context) { var items = list.selectAll('li') .data(memberships, function(d) { - return Entity.key(d.relation) + ',' + d.index + ',' + - (d.member ? Entity.key(d.member) : 'incomplete'); + return coreEntity.key(d.relation) + ',' + d.index + ',' + + (d.member ? coreEntity.key(d.member) : 'incomplete'); }); items.exit() @@ -91,19 +96,19 @@ export function RawMemberEditor(context) { enter.each(function(d) { if (d.member) { - var $label = d3.select(this).append('label') + var label = d3.select(this).append('label') .attr('class', 'form-label') .append('a') .attr('href', '#') .on('click', selectMember); - $label.append('span') + label.append('span') .attr('class', 'member-entity-type') .text(function(d) { return context.presets().match(d.member, context.graph()).name(); }); - $label.append('span') + label.append('span') .attr('class', 'member-entity-name') - .text(function(d) { return displayName(d.member); }); + .text(function(d) { return utilDisplayName(d.member); }); } else { d3.select(this).append('label') @@ -112,7 +117,8 @@ export function RawMemberEditor(context) { } }); - enter.append('input') + enter + .append('input') .attr('class', 'member-role') .property('type', 'text') .attr('maxlength', 255) @@ -120,11 +126,12 @@ export function RawMemberEditor(context) { .property('value', function(d) { return d.role; }) .on('change', changeRole); - enter.append('button') + enter + .append('button') .attr('tabindex', -1) .attr('class', 'remove button-input-action member-delete minor') .on('click', deleteMember) - .call(Icon('#operation-delete')); + .call(svgIcon('#operation-delete')); if (context.taginfo()) { enter.each(bindTypeahead); @@ -162,6 +169,7 @@ export function RawMemberEditor(context) { })); } + function unbind() { var row = d3.select(this); diff --git a/modules/ui/raw_membership_editor.js b/modules/ui/raw_membership_editor.js index 561c2bb36..ed43a0fe4 100644 --- a/modules/ui/raw_membership_editor.js +++ b/modules/ui/raw_membership_editor.js @@ -2,29 +2,37 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3combobox } from '../lib/d3.combobox.js'; import { t } from '../util/locale'; -import { AddEntity, AddMember, ChangeMember, DeleteMember } from '../actions/index'; -import { Entity, Relation } from '../core/index'; -import { Disclosure } from './disclosure'; -import { Icon } from '../svg/index'; -import { Select } from '../modes/index'; -import { displayName } from '../util/index'; + +import { + actionAddEntity, + actionAddMember, + actionChangeMember, + actionDeleteMember +} from '../actions/index'; + +import { coreEntity, coreRelation } from '../core/index'; +import { modeSelect } from '../modes/index'; +import { svgIcon } from '../svg/index'; +import { uiDisclosure } from './disclosure'; +import { utilDisplayName } from '../util/index'; -export function RawMembershipEditor(context) { +export function uiRawMembershipEditor(context) { var id, showBlank; function selectRelation(d) { d3.event.preventDefault(); - context.enter(Select(context, [d.relation.id])); + context.enter(modeSelect(context, [d.relation.id])); } function changeRole(d) { var role = d3.select(this).property('value'); context.perform( - ChangeMember(d.relation.id, _.extend({}, d.member, {role: role}), d.index), - t('operations.change_role.annotation')); + actionChangeMember(d.relation.id, _.extend({}, d.member, { role: role }), d.index), + t('operations.change_role.annotation') + ); } @@ -33,26 +41,29 @@ export function RawMembershipEditor(context) { if (d.relation) { context.perform( - AddMember(d.relation.id, {id: id, type: context.entity(id).type, role: role}), - t('operations.add_member.annotation')); + actionAddMember(d.relation.id, { id: id, type: context.entity(id).type, role: role }), + t('operations.add_member.annotation') + ); } else { - var relation = Relation(); + var relation = coreRelation(); context.perform( - AddEntity(relation), - AddMember(relation.id, {id: id, type: context.entity(id).type, role: role}), - t('operations.add.annotation.relation')); + actionAddEntity(relation), + actionAddMember(relation.id, { id: id, type: context.entity(id).type, role: role }), + t('operations.add.annotation.relation') + ); - context.enter(Select(context, [relation.id])); + context.enter(modeSelect(context, [relation.id])); } } function deleteMembership(d) { context.perform( - DeleteMember(d.relation.id, d.index), - t('operations.delete_member.annotation')); + actionDeleteMember(d.relation.id, d.index), + t('operations.delete_member.annotation') + ); } @@ -69,7 +80,7 @@ export function RawMembershipEditor(context) { return; var presetName = context.presets().match(entity, graph).name(), - entityName = displayName(entity) || ''; + entityName = utilDisplayName(entity) || ''; var value = presetName + ' ' + entityName; if (q && value.toLowerCase().indexOf(q.toLowerCase()) === -1) @@ -82,7 +93,7 @@ export function RawMembershipEditor(context) { }); result.sort(function(a, b) { - return Relation.creationOrder(a.relation, b.relation); + return coreRelation.creationOrder(a.relation, b.relation); }); // Dedupe identical names by appending relation id - see #2891 @@ -98,7 +109,6 @@ export function RawMembershipEditor(context) { }); result.unshift(newRelation); - return result; } @@ -115,11 +125,12 @@ export function RawMembershipEditor(context) { }); }); - selection.call(Disclosure() + selection.call(uiDisclosure() .title(t('inspector.all_relations') + ' (' + memberships.length + ')') .expanded(true) .on('toggled', toggled) - .content(content)); + .content(content) + ); function toggled(expanded) { @@ -140,7 +151,9 @@ export function RawMembershipEditor(context) { var items = list.selectAll('li.member-row-normal') - .data(memberships, function(d) { return Entity.key(d.relation) + ',' + d.index; }); + .data(memberships, function(d) { + return coreEntity.key(d.relation) + ',' + d.index; + }); items.exit() .each(unbind) @@ -160,12 +173,14 @@ export function RawMembershipEditor(context) { label .append('span') .attr('class', 'member-entity-type') - .text(function(d) { return context.presets().match(d.relation, context.graph()).name(); }); + .text(function(d) { + return context.presets().match(d.relation, context.graph()).name(); + }); label .append('span') .attr('class', 'member-entity-name') - .text(function(d) { return displayName(d.relation); }); + .text(function(d) { return utilDisplayName(d.relation); }); enter .append('input') @@ -181,7 +196,7 @@ export function RawMembershipEditor(context) { .attr('tabindex', -1) .attr('class', 'remove button-input-action member-delete minor') .on('click', deleteMembership) - .call(Icon('#operation-delete')); + .call(svgIcon('#operation-delete')); if (context.taginfo()) { enter.each(bindTypeahead); @@ -205,7 +220,8 @@ export function RawMembershipEditor(context) { .fetcher(function(value, callback) { callback(relations(value)); }) .on('accept', function(d) { addMembership(d, list.selectAll('.member-row-new .member-role').property('value')); - })); + }) + ); enter .append('input') @@ -220,7 +236,7 @@ export function RawMembershipEditor(context) { .attr('tabindex', -1) .attr('class', 'remove button-input-action member-delete minor') .on('click', deleteMembership) - .call(Icon('#operation-delete')); + .call(svgIcon('#operation-delete')); } else { list.selectAll('.member-row-new') @@ -239,7 +255,7 @@ export function RawMembershipEditor(context) { content(wrap); list.selectAll('.member-entity-input').node().focus(); }) - .call(Icon('#icon-plus', 'light')); + .call(svgIcon('#icon-plus', 'light')); function bindTypeahead(d) { diff --git a/modules/ui/raw_tag_editor.js b/modules/ui/raw_tag_editor.js index 06a772c3a..65bf0df74 100644 --- a/modules/ui/raw_tag_editor.js +++ b/modules/ui/raw_tag_editor.js @@ -1,14 +1,14 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; -import { getSetValue } from '../util/get_set_value'; import { d3combobox } from '../lib/d3.combobox.js'; import { t } from '../util/locale'; -import { Disclosure } from './disclosure'; -import { Icon } from '../svg/index'; -import { TagReference } from './tag_reference'; +import { svgIcon } from '../svg/index'; +import { uiDisclosure } from './disclosure'; +import { uiTagReference } from './tag_reference'; +import { utilGetSetValue } from '../util/get_set_value'; +import { utilRebind } from '../util/rebind'; -export function RawTagEditor(context) { +export function uiRawTagEditor(context) { var dispatch = d3.dispatch('change'), showBlank = false, state, @@ -20,7 +20,7 @@ export function RawTagEditor(context) { function rawTagEditor(selection) { var count = Object.keys(tags).filter(function(d) { return d; }).length; - selection.call(Disclosure() + selection.call(uiDisclosure() .title(t('inspector.all_tags') + ' (' + count + ')') .expanded(context.storage('raw_tag_editor.expanded') === 'true' || preset.isFallback()) .on('toggled', toggled) @@ -58,7 +58,7 @@ export function RawTagEditor(context) { .append('button') .attr('class', 'add-tag') .on('click', addTag) - .call(Icon('#icon-plus', 'light')); + .call(svgIcon('#icon-plus', 'light')); var items = list.selectAll('.tag-row') @@ -99,7 +99,7 @@ export function RawTagEditor(context) { .append('button') .attr('tabindex', -1) .attr('class', 'remove minor') - .call(Icon('#operation-delete')); + .call(svgIcon('#operation-delete')); // Update @@ -126,9 +126,9 @@ export function RawTagEditor(context) { reference; if (isRelation && tag.key === 'type') { - reference = TagReference({rtype: tag.value}, context); + reference = uiTagReference({ rtype: tag.value }, context); } else { - reference = TagReference({key: tag.key, value: tag.value}, context); + reference = uiTagReference({ key: tag.key, value: tag.value }, context); } if (state === 'hover') { @@ -142,11 +142,11 @@ export function RawTagEditor(context) { items.selectAll('input.key') .attr('title', function(d) { return d.key; }) - .call(getSetValue, function(d) { return d.key; }); + .call(utilGetSetValue, function(d) { return d.key; }); items.selectAll('input.value') .attr('title', function(d) { return d.value; }) - .call(getSetValue, function(d) { return d.value; }); + .call(utilGetSetValue, function(d) { return d.value; }); items.selectAll('button.remove') .on('click', removeTag); @@ -190,7 +190,7 @@ export function RawTagEditor(context) { .fetcher(function(value, callback) { context.taginfo().values({ debounce: true, - key: getSetValue(key), + key: utilGetSetValue(key), geometry: context.geometry(id), query: value }, function(err, data) { @@ -288,5 +288,5 @@ export function RawTagEditor(context) { }; - return rebind(rawTagEditor, dispatch, 'on'); + return utilRebind(rawTagEditor, dispatch, 'on'); } diff --git a/modules/ui/restore.js b/modules/ui/restore.js index 7dbcbd877..f67cdd1d3 100644 --- a/modules/ui/restore.js +++ b/modules/ui/restore.js @@ -1,34 +1,41 @@ import { t } from '../util/locale'; -import { modal } from './modal'; +import { uiModal } from './modal'; + + +export function uiRestore(context) { -export function Restore(context) { return function(selection) { if (!context.history().lock() || !context.history().restorableChanges()) return; - var modalSelection = modal(selection, true); + var modalSelection = uiModal(selection, true); modalSelection.select('.modal') .attr('class', 'modal fillL col6'); var introModal = modalSelection.select('.content'); - introModal.attr('class','cf'); + introModal + .attr('class','cf'); - introModal.append('div') + introModal + .append('div') .attr('class', 'modal-section') .append('h3') .text(t('restore.heading')); - introModal.append('div') + introModal + .append('div') .attr('class','modal-section') .append('p') .text(t('restore.description')); - var buttonWrap = introModal.append('div') + var buttonWrap = introModal + .append('div') .attr('class', 'modal-actions cf'); - var restore = buttonWrap.append('button') + var restore = buttonWrap + .append('button') .attr('class', 'restore col6') .text(t('restore.restore')) .on('click', function() { @@ -36,7 +43,8 @@ export function Restore(context) { modalSelection.remove(); }); - buttonWrap.append('button') + buttonWrap + .append('button') .attr('class', 'reset col6') .text(t('restore.reset')) .on('click', function() { diff --git a/modules/ui/save.js b/modules/ui/save.js index 09fe0ef88..3f8746576 100644 --- a/modules/ui/save.js +++ b/modules/ui/save.js @@ -1,27 +1,30 @@ import * as d3 from 'd3'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; +import { modeSave } from '../modes/index'; +import { uiCmd } from './cmd'; +import { uiTooltipHtml } from './tooltipHtml'; import { tooltip } from '../util/tooltip'; -import { Save as SaveMode } from '../modes/index'; -import { cmd } from './cmd'; -import { tooltipHtml } from './tooltipHtml'; -export function Save(context) { + +export function uiSave(context) { var history = context.history(), - key = cmd('⌘S'); + key = uiCmd('⌘S'); function saving() { return context.mode().id === 'save'; } + function save() { d3.event.preventDefault(); if (!context.inIntro() && !saving() && history.hasChanges()) { - context.enter(SaveMode(context)); + context.enter(modeSave(context)); } } + function getBackground(numChanges) { var step; if (numChanges === 0) { @@ -35,23 +38,27 @@ export function Save(context) { } } + return function(selection) { var tooltipBehavior = tooltip() .placement('bottom') .html(true) .title(tooltipHtml(t('save.no_changes'), key)); - var button = selection.append('button') + var button = selection + .append('button') .attr('class', 'save col12 disabled') .attr('tabindex', -1) .on('click', save) .call(tooltipBehavior); - button.append('span') + button + .append('span') .attr('class', 'label') .text(t('save.title')); - button.append('span') + button + .append('span') .attr('class', 'count') .text('0'); @@ -63,31 +70,33 @@ export function Save(context) { var numChanges = 0; - context.history().on('change.save', function() { - var _ = history.difference().summary().length; - if (_ === numChanges) - return; - numChanges = _; + context.history() + .on('change.save', function() { + var _ = history.difference().summary().length; + if (_ === numChanges) + return; + numChanges = _; - tooltipBehavior.title(tooltipHtml(t(numChanges > 0 ? - 'save.help' : 'save.no_changes'), key)); + tooltipBehavior.title(tooltipHtml(t(numChanges > 0 ? + 'save.help' : 'save.no_changes'), key)); - var background = getBackground(numChanges); + var background = getBackground(numChanges); - button - .classed('disabled', numChanges === 0) - .classed('has-count', numChanges > 0) - .style('background', background); + button + .classed('disabled', numChanges === 0) + .classed('has-count', numChanges > 0) + .style('background', background); - button.select('span.count') - .text(numChanges) - .style('background', background) - .style('border-color', background); - }); + button.select('span.count') + .text(numChanges) + .style('background', background) + .style('border-color', background); + }); - context.on('enter.save', function() { - button.property('disabled', saving()); - if (saving()) button.call(tooltipBehavior.hide); - }); + context + .on('enter.save', function() { + button.property('disabled', saving()); + if (saving()) button.call(tooltipBehavior.hide); + }); }; } diff --git a/modules/ui/scale.js b/modules/ui/scale.js index 179a4f4f8..948724c76 100644 --- a/modules/ui/scale.js +++ b/modules/ui/scale.js @@ -1,20 +1,22 @@ -import { lonToMeters, metersToLon } from '../geo/index'; -import { Detect } from '../util/detect'; +import { geoLonToMeters, geoMetersToLon } from '../geo/index'; +import { utilDetect } from '../util/detect'; -export function Scale(context) { + +export function uiScale(context) { var projection = context.projection, - imperial = (Detect().locale.toLowerCase() === 'en-us'), + isImperial = (utilDetect().locale.toLowerCase() === 'en-us'), maxLength = 180, tickHeight = 8; + function scaleDefs(loc1, loc2) { var lat = (loc2[1] + loc1[1]) / 2, - conversion = (imperial ? 3.28084 : 1), - dist = lonToMeters(loc2[0] - loc1[0], lat) * conversion, + conversion = (isImperial ? 3.28084 : 1), + dist = geoLonToMeters(loc2[0] - loc1[0], lat) * conversion, scale = { dist: 0, px: 0, text: '' }, buckets, i, val, dLon; - if (imperial) { + if (isImperial) { buckets = [5280000, 528000, 52800, 5280, 500, 50, 5, 1]; } else { buckets = [5000000, 500000, 50000, 5000, 500, 50, 5, 1]; @@ -29,10 +31,10 @@ export function Scale(context) { } } - dLon = metersToLon(scale.dist / conversion, lat); + dLon = geoMetersToLon(scale.dist / conversion, lat); scale.px = Math.round(projection([loc1[0] + dLon, loc1[1]])[0]); - if (imperial) { + if (isImperial) { if (scale.dist >= 5280) { scale.dist /= 5280; scale.text = String(scale.dist) + ' mi'; @@ -51,6 +53,7 @@ export function Scale(context) { return scale; } + function update(selection) { // choose loc1, loc2 along bottom of viewport (near where the scale will be drawn) var dims = context.map().dimensions(), @@ -70,7 +73,7 @@ export function Scale(context) { return function(selection) { function switchUnits() { - imperial = !imperial; + isImperial = !isImperial; selection.call(update); } diff --git a/modules/ui/selection_list.js b/modules/ui/selection_list.js index f72f73f87..8ddc522b5 100644 --- a/modules/ui/selection_list.js +++ b/modules/ui/selection_list.js @@ -1,15 +1,15 @@ import * as d3 from 'd3'; import { t } from '../util/locale'; -import { Entity } from '../core/index'; -import { Icon } from '../svg/index'; -import { Select } from '../modes/index'; -import { displayName } from '../util/index'; +import { coreEntity } from '../core/index'; +import { svgIcon } from '../svg/index'; +import { modeSelect } from '../modes/index'; +import { utilDisplayName } from '../util/index'; -export function SelectionList(context, selectedIDs) { +export function uiSelectionList(context, selectedIDs) { function selectEntity(entity) { - context.enter(Select(context, [entity.id]).suppressMenu(true)); + context.enter(modeSelect(context, [entity.id]).suppressMenu(true)); } @@ -19,7 +19,7 @@ export function SelectionList(context, selectedIDs) { if (index > -1) { selectedIDs.splice(index, 1); } - context.enter(Select(context, selectedIDs).suppressMenu(true)); + context.enter(modeSelect(context, selectedIDs).suppressMenu(true)); } @@ -52,7 +52,7 @@ export function SelectionList(context, selectedIDs) { .filter(function(entity) { return entity; }); var items = list.selectAll('.feature-list-item') - .data(entities, Entity.key); + .data(entities, coreEntity.key); items.exit() .remove(); @@ -71,12 +71,12 @@ export function SelectionList(context, selectedIDs) { .append('button') .attr('class', 'close') .on('click', deselectEntity) - .call(Icon('#icon-close')); + .call(svgIcon('#icon-close')); label .append('span') .attr('class', 'entity-geom-icon') - .call(Icon('', 'pre-text')); + .call(svgIcon('', 'pre-text')); label .append('span') @@ -99,7 +99,7 @@ export function SelectionList(context, selectedIDs) { .text(function(entity) { return context.presets().match(entity, context.graph()).name(); }); items.selectAll('.entity-name') - .text(function(entity) { return displayName(entity); }); + .text(function(entity) { return utilDisplayName(entity); }); } } diff --git a/modules/ui/sidebar.js b/modules/ui/sidebar.js index a17d8662b..a84660d05 100644 --- a/modules/ui/sidebar.js +++ b/modules/ui/sidebar.js @@ -1,26 +1,35 @@ import _ from 'lodash'; -import { FeatureList } from './feature_list'; -import { Inspector } from './inspector'; -import { Notice } from './notice'; +import { uiFeatureList } from './feature_list'; +import { uiInspector } from './inspector'; +import { uiNotice } from './notice'; -export function Sidebar(context) { - var inspector = Inspector(context), + +export function uiSidebar(context) { + var inspector = uiInspector(context), current; + function sidebar(selection) { - var featureListWrap = selection.append('div') + var featureListWrap = selection + .append('div') .attr('class', 'feature-list-pane') - .call(FeatureList(context)); + .call(uiFeatureList(context)); - selection.call(Notice(context)); + selection + .call(uiNotice(context)); - var inspectorWrap = selection.append('div') + var inspectorWrap = selection + .append('div') .attr('class', 'inspector-hidden inspector-wrap fr'); + function hover(id) { if (!current && context.hasEntity(id)) { - featureListWrap.classed('inspector-hidden', true); - inspectorWrap.classed('inspector-hidden', false) + featureListWrap + .classed('inspector-hidden', true); + + inspectorWrap + .classed('inspector-hidden', false) .classed('inspector-hover', true); if (inspector.entityID() !== id || inspector.state() !== 'hover') { @@ -28,21 +37,31 @@ export function Sidebar(context) { .state('hover') .entityID(id); - inspectorWrap.call(inspector); + inspectorWrap + .call(inspector); } + } else if (!current) { - featureListWrap.classed('inspector-hidden', false); - inspectorWrap.classed('inspector-hidden', true); - inspector.state('hide'); + featureListWrap + .classed('inspector-hidden', false); + inspectorWrap + .classed('inspector-hidden', true); + inspector + .state('hide'); } } + sidebar.hover = _.throttle(hover, 200); + sidebar.select = function(id, newFeature) { if (!current && id) { - featureListWrap.classed('inspector-hidden', true); - inspectorWrap.classed('inspector-hidden', false) + featureListWrap + .classed('inspector-hidden', true); + + inspectorWrap + .classed('inspector-hidden', false) .classed('inspector-hover', false); if (inspector.entityID() !== id || inspector.state() !== 'select') { @@ -51,32 +70,47 @@ export function Sidebar(context) { .entityID(id) .newFeature(newFeature); - inspectorWrap.call(inspector); + inspectorWrap + .call(inspector); } + } else if (!current) { - featureListWrap.classed('inspector-hidden', false); - inspectorWrap.classed('inspector-hidden', true); - inspector.state('hide'); + featureListWrap + .classed('inspector-hidden', false); + inspectorWrap + .classed('inspector-hidden', true); + inspector + .state('hide'); } }; + sidebar.show = function(component) { - featureListWrap.classed('inspector-hidden', true); - inspectorWrap.classed('inspector-hidden', true); + featureListWrap + .classed('inspector-hidden', true); + inspectorWrap + .classed('inspector-hidden', true); + if (current) current.remove(); - current = selection.append('div') + current = selection + .append('div') .attr('class', 'sidebar-component') .call(component); }; + sidebar.hide = function() { - featureListWrap.classed('inspector-hidden', false); - inspectorWrap.classed('inspector-hidden', true); + featureListWrap + .classed('inspector-hidden', false); + inspectorWrap + .classed('inspector-hidden', true); + if (current) current.remove(); current = null; }; } + sidebar.hover = function() {}; sidebar.hover.cancel = function() {}; sidebar.select = function() {}; diff --git a/modules/ui/source_switch.js b/modules/ui/source_switch.js index fd16ae425..3a534bec0 100644 --- a/modules/ui/source_switch.js +++ b/modules/ui/source_switch.js @@ -1,10 +1,12 @@ import * as d3 from 'd3'; import { t } from '../util/locale'; -import { Browse } from '../modes/index'; +import { modeBrowse } from '../modes/index'; -export function SourceSwitch(context) { + +export function uiSourceSwitch(context) { var keys; + function click() { d3.event.preventDefault(); @@ -17,7 +19,7 @@ export function SourceSwitch(context) { context.connection() .switch(live ? keys[1] : keys[0]); - context.enter(Browse(context)); + context.enter(modeBrowse(context)); context.flush(); d3.select(this) @@ -35,11 +37,13 @@ export function SourceSwitch(context) { .on('click', click); }; + sourceSwitch.keys = function(_) { if (!arguments.length) return keys; keys = _; return sourceSwitch; }; + return sourceSwitch; } diff --git a/modules/ui/spinner.js b/modules/ui/spinner.js index a995a2b50..9daaf4b92 100644 --- a/modules/ui/spinner.js +++ b/modules/ui/spinner.js @@ -1,20 +1,23 @@ -export function Spinner(context) { +export function uiSpinner(context) { var connection = context.connection(); + return function(selection) { var img = selection .append('img') .attr('src', context.imagePath('loader-black.gif')) .style('opacity', 0); - connection.on('loading.spinner', function() { - img.transition() - .style('opacity', 1); - }); + connection + .on('loading.spinner', function() { + img.transition() + .style('opacity', 1); + }); - connection.on('loaded.spinner', function() { - img.transition() - .style('opacity', 0); - }); + connection + .on('loaded.spinner', function() { + img.transition() + .style('opacity', 0); + }); }; } diff --git a/modules/ui/splash.js b/modules/ui/splash.js index 4774668dd..791d17693 100644 --- a/modules/ui/splash.js +++ b/modules/ui/splash.js @@ -1,16 +1,18 @@ import * as d3 from 'd3'; import { t } from '../util/locale'; -import { intro } from './intro/index'; -import { modal } from './modal'; +import { uiIntro } from './intro/index'; +import { uiModal } from './modal'; + + +export function uiSplash(context) { -export function Splash(context) { return function(selection) { if (context.storage('sawSplash')) return; context.storage('sawSplash', true); - var modalSelection = modal(selection); + var modalSelection = uiModal(selection); modalSelection.select('.modal') .attr('class', 'modal-splash modal col6'); @@ -19,11 +21,13 @@ export function Splash(context) { .append('div') .attr('class', 'fillL'); - introModal.append('div') + introModal + .append('div') .attr('class','modal-section cf') .append('h3').text(t('splash.welcome')); - introModal.append('div') + introModal + .append('div') .attr('class','modal-section') .append('p') .html(t('splash.text', { @@ -32,22 +36,27 @@ export function Splash(context) { github: 'github.com' })); - var buttons = introModal.append('div').attr('class', 'modal-actions cf'); + var buttons = introModal + .append('div') + .attr('class', 'modal-actions cf'); - buttons.append('button') + buttons + .append('button') .attr('class', 'col6 walkthrough') .text(t('splash.walkthrough')) .on('click', function() { - d3.select(document.body).call(intro(context)); + d3.select(document.body).call(uiIntro(context)); modalSelection.close(); }); - buttons.append('button') + buttons + .append('button') .attr('class', 'col6 start') .text(t('splash.start')) .on('click', modalSelection.close); - modalSelection.select('button.close').attr('class','hide'); + modalSelection.select('button.close') + .attr('class','hide'); }; } diff --git a/modules/ui/status.js b/modules/ui/status.js index 9e0a201bf..6bdb4e7a8 100644 --- a/modules/ui/status.js +++ b/modules/ui/status.js @@ -1,36 +1,32 @@ import { t } from '../util/locale'; -export function Status(context) { +export function uiStatus(context) { var connection = context.connection(), errCount = 0; return function(selection) { function update() { - connection.status(function(err, apiStatus) { - selection.html(''); - if (err && errCount++ < 2) return; if (err) { selection.text(t('status.error')); - } else if (apiStatus === 'readonly') { selection.text(t('status.readonly')); - } else if (apiStatus === 'offline') { selection.text(t('status.offline')); } selection.attr('class', 'api-status ' + (err ? 'error' : apiStatus)); if (!err) errCount = 0; - }); } - connection.on('auth', function() { update(selection); }); + connection + .on('auth', function() { update(selection); }); + window.setInterval(update, 90000); update(selection); }; diff --git a/modules/ui/success.js b/modules/ui/success.js index 45aef323b..f182119aa 100644 --- a/modules/ui/success.js +++ b/modules/ui/success.js @@ -1,46 +1,55 @@ import * as d3 from 'd3'; -import { rebind } from '../util/rebind'; import { t } from '../util/locale'; import { tooltip } from '../util/tooltip'; -import { Icon } from '../svg/index'; +import { svgIcon } from '../svg/index'; +import { utilRebind } from '../util/rebind'; -export function Success(context) { + +export function uiSuccess(context) { var dispatch = d3.dispatch('cancel'), changeset; + function success(selection) { var message = (changeset.comment || t('success.edited_osm')).substring(0, 130) + ' ' + context.connection().changesetURL(changeset.id); - var header = selection.append('div') + var header = selection + .append('div') .attr('class', 'header fillL'); - header.append('button') + header + .append('button') .attr('class', 'fr') .on('click', function() { dispatch.call('cancel'); }) - .call(Icon('#icon-close')); + .call(svgIcon('#icon-close')); - header.append('h3') + header + .append('h3') .text(t('success.just_edited')); - var body = selection.append('div') + var body = selection + .append('div') .attr('class', 'body save-success fillL'); - body.append('p') + body + .append('p') .html(t('success.help_html')); - body.append('a') + body + .append('a') .attr('class', 'details') .attr('target', '_blank') .attr('tabindex', -1) - .call(Icon('#icon-out-link', 'inline')) + .call(svgIcon('#icon-out-link', 'inline')) .attr('href', t('success.help_link_url')) .append('span') .text(t('success.help_link_text')); var changesetURL = context.connection().changesetURL(changeset.id); - body.append('a') + body + .append('a') .attr('class', 'button col12 osm') .attr('target', '_blank') .attr('href', changesetURL) @@ -62,14 +71,16 @@ export function Success(context) { .call(tooltip() .title(function(d) { return t('success.' + d.key); }) .placement('bottom')) - .each(function(d) { d3.select(this).call(Icon('#logo-' + d.key, 'social')); }); + .each(function(d) { d3.select(this).call(svgIcon('#logo-' + d.key, 'social')); }); } + success.changeset = function(_) { if (!arguments.length) return changeset; changeset = _; return success; }; - return rebind(success, dispatch, 'on'); + + return utilRebind(success, dispatch, 'on'); } diff --git a/modules/ui/tag_reference.js b/modules/ui/tag_reference.js index 3345c0250..ca8c1ab07 100644 --- a/modules/ui/tag_reference.js +++ b/modules/ui/tag_reference.js @@ -1,10 +1,11 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { t } from '../util/locale'; -import { Detect } from '../util/detect'; -import { Icon } from '../svg/index'; +import { utilDetect } from '../util/detect'; +import { svgIcon } from '../svg/index'; -export function TagReference(tag, context) { + +export function uiTagReference(tag, context) { var tagReference = {}, button, body, @@ -13,7 +14,7 @@ export function TagReference(tag, context) { function findLocal(data) { - var locale = Detect().locale.toLowerCase(), + var locale = utilDetect().locale.toLowerCase(), localized; localized = _.find(data, function(d) { @@ -79,7 +80,7 @@ export function TagReference(tag, context) { .attr('target', '_blank') .attr('tabindex', -1) .attr('href', 'https://wiki.openstreetmap.org/wiki/' + docs.title) - .call(Icon('#icon-out-link', 'inline')) + .call(svgIcon('#icon-out-link', 'inline')) .append('span') .text(t('inspector.reference')); }); @@ -132,7 +133,7 @@ export function TagReference(tag, context) { }) .attr('class', 'tag-reference-button') .attr('tabindex', -1) - .call(Icon('#icon-inspect')) + .call(svgIcon('#icon-inspect')) .merge(button); }; diff --git a/modules/ui/toggle.js b/modules/ui/toggle.js index 4a8d16c5e..708b4157e 100644 --- a/modules/ui/toggle.js +++ b/modules/ui/toggle.js @@ -4,7 +4,7 @@ import * as d3 from 'd3'; // hide class, which sets display=none, and a d3 transition for opacity. // this will cause blinking when called repeatedly, so check that the // value actually changes between calls. -export function Toggle(show, callback) { +export function uiToggle(show, callback) { return function(selection) { selection .style('opacity', show ? 0 : 1) diff --git a/modules/ui/tooltipHtml.js b/modules/ui/tooltipHtml.js index c8299a740..9553f6f02 100644 --- a/modules/ui/tooltipHtml.js +++ b/modules/ui/tooltipHtml.js @@ -1,6 +1,6 @@ import { t } from '../util/locale'; -export function tooltipHtml(text, key) { +export function uiTooltipHtml(text, key) { var s = '' + text + ''; if (key) { s += '
' + diff --git a/modules/ui/undo_redo.js b/modules/ui/undo_redo.js index a56e76d88..ed2dd0643 100644 --- a/modules/ui/undo_redo.js +++ b/modules/ui/undo_redo.js @@ -1,49 +1,54 @@ import * as d3 from 'd3'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; +import { svgIcon } from '../svg/index'; +import { uiCmd } from './cmd'; +import { uiTooltipHtml } from './tooltipHtml'; import { tooltip } from '../util/tooltip'; -import { Icon } from '../svg/index'; -import { cmd } from './cmd'; -import { tooltipHtml } from './tooltipHtml'; -export function UndoRedo(context) { + +export function uiUndoRedo(context) { var commands = [{ id: 'undo', - cmd: cmd('⌘Z'), + cmd: uiCmd('⌘Z'), action: function() { if (!(context.inIntro() || saving())) context.undo(); }, annotation: function() { return context.history().undoAnnotation(); } }, { id: 'redo', - cmd: cmd('⌘⇧Z'), + cmd: uiCmd('⌘⇧Z'), action: function() {if (!(context.inIntro() || saving())) context.redo(); }, annotation: function() { return context.history().redoAnnotation(); } }]; + function saving() { return context.mode().id === 'save'; } + return function(selection) { var tooltipBehavior = tooltip() .placement('bottom') .html(true) .title(function (d) { - return tooltipHtml(d.annotation() ? + return uiTooltipHtml(d.annotation() ? t(d.id + '.tooltip', {action: d.annotation()}) : t(d.id + '.nothing'), d.cmd); }); var buttons = selection.selectAll('button') .data(commands) - .enter().append('button') + .enter() + .append('button') .attr('class', 'col6 disabled') .on('click', function(d) { return d.action(); }) .call(tooltipBehavior); - buttons.each(function(d) { - d3.select(this) - .call(Icon('#icon-' + d.id)); - }); + buttons + .each(function(d) { + d3.select(this) + .call(svgIcon('#icon-' + d.id)); + }); var keybinding = d3keybinding('undo') .on(commands[0].cmd, function() { d3.event.preventDefault(); commands[0].action(); }) diff --git a/modules/ui/view_on_osm.js b/modules/ui/view_on_osm.js index 87d77b0fd..478c6eb76 100644 --- a/modules/ui/view_on_osm.js +++ b/modules/ui/view_on_osm.js @@ -1,7 +1,8 @@ import { t } from '../util/locale'; -import { Icon } from '../svg/index'; +import { svgIcon } from '../svg/index'; -export function ViewOnOSM(context) { + +export function uiViewOnOSM(context) { var id; function viewOnOSM(selection) { @@ -9,21 +10,22 @@ export function ViewOnOSM(context) { selection.style('display', entity.isNew() ? 'none' : null); - var $link = selection.selectAll('.view-on-osm') + var link = selection.selectAll('.view-on-osm') .data([0]); - $link.enter() + link.enter() .append('a') .attr('class', 'view-on-osm') .attr('target', '_blank') - .call(Icon('#icon-out-link', 'inline')) + .call(svgIcon('#icon-out-link', 'inline')) .append('span') .text(t('inspector.view_on_osm')); - $link + link .attr('href', context.connection().entityURL(entity)); } + viewOnOSM.entityID = function(_) { if (!arguments.length) return id; id = _; diff --git a/modules/ui/zoom.js b/modules/ui/zoom.js index 864df3344..c8fe72ea0 100644 --- a/modules/ui/zoom.js +++ b/modules/ui/zoom.js @@ -2,12 +2,13 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; +import { svgIcon } from '../svg/index'; +import { uiCmd } from './cmd'; +import { uiTooltipHtml } from './tooltipHtml'; import { tooltip } from '../util/tooltip'; -import { Icon } from '../svg/index'; -import { cmd } from './cmd'; -import { tooltipHtml } from './tooltipHtml'; -export function Zoom(context) { + +export function uiZoom(context) { var zooms = [{ id: 'zoom-in', icon: 'plus', @@ -22,21 +23,25 @@ export function Zoom(context) { key: '-' }]; + function zoomIn() { d3.event.preventDefault(); if (!context.inIntro()) context.zoomIn(); } + function zoomOut() { d3.event.preventDefault(); if (!context.inIntro()) context.zoomOut(); } + function zoomInFurther() { d3.event.preventDefault(); if (!context.inIntro()) context.zoomInFurther(); } + function zoomOutFurther() { d3.event.preventDefault(); if (!context.inIntro()) context.zoomOutFurther(); @@ -46,7 +51,8 @@ export function Zoom(context) { return function(selection) { var button = selection.selectAll('button') .data(zooms) - .enter().append('button') + .enter() + .append('button') .attr('tabindex', -1) .attr('class', function(d) { return d.id; }) .on('click.editor', function(d) { d.action(); }) @@ -54,12 +60,13 @@ export function Zoom(context) { .placement('left') .html(true) .title(function(d) { - return tooltipHtml(d.title, d.key); - })); + return uiTooltipHtml(d.title, d.key); + }) + ); button.each(function(d) { d3.select(this) - .call(Icon('#icon-' + d.icon, 'light')); + .call(svgIcon('#icon-' + d.icon, 'light')); }); var keybinding = d3keybinding('zoom'); @@ -67,14 +74,14 @@ export function Zoom(context) { _.each(['=','ffequals','plus','ffplus'], function(key) { keybinding.on(key, zoomIn); keybinding.on('⇧' + key, zoomIn); - keybinding.on(cmd('⌘' + key), zoomInFurther); - keybinding.on(cmd('⌘⇧' + key), zoomInFurther); + keybinding.on(uiCmd('⌘' + key), zoomInFurther); + keybinding.on(uiCmd('⌘⇧' + key), zoomInFurther); }); _.each(['-','ffminus','_','dash'], function(key) { keybinding.on(key, zoomOut); keybinding.on('⇧' + key, zoomOut); - keybinding.on(cmd('⌘' + key), zoomOutFurther); - keybinding.on(cmd('⌘⇧' + key), zoomOutFurther); + keybinding.on(uiCmd('⌘' + key), zoomOutFurther); + keybinding.on(uiCmd('⌘⇧' + key), zoomOutFurther); }); d3.select(document) diff --git a/modules/util/bind_once.js b/modules/util/bind_once.js index 67905e89a..7a7164af1 100644 --- a/modules/util/bind_once.js +++ b/modules/util/bind_once.js @@ -1,4 +1,4 @@ -export function bindOnce(target, type, listener, capture) { +export function utilBindOnce(target, type, listener, capture) { var typeOnce = type + '.once'; function one() { target.on(typeOnce, null); diff --git a/modules/util/curtain.js b/modules/util/curtain.js index 5fa781e22..8cd189eac 100644 --- a/modules/util/curtain.js +++ b/modules/util/curtain.js @@ -1,7 +1,8 @@ import * as d3 from 'd3'; -import { getDimensions } from './dimensions'; -import { rebind } from './rebind'; -import { Toggle } from '../ui/toggle'; +import { utilGetDimensions } from './dimensions'; +import { utilRebind } from './rebind'; +import { uiToggle } from '../ui/toggle'; + // Tooltips and svg mask used to highlight certain features export function d3curtain() { @@ -36,14 +37,16 @@ export function d3curtain() { resize(); + function resize() { surface - .attr('width', window.innerWidth) - .attr('height', window.innerHeight); + .attr('width', window.innerWidth) + .attr('height', window.innerHeight); curtain.cut(darkness.datum()); } } + curtain.reveal = function(box, text, tooltipclass, duration) { if (typeof box === 'string') box = d3.select(box).node(); if (box.getBoundingClientRect) box = box.getBoundingClientRect(); @@ -56,9 +59,9 @@ export function d3curtain() { var html = parts[0] ? '' + parts[0] + '' : ''; if (parts[1]) html += '' + parts[1] + ''; - var dimensions = getDimensions(tooltip.classed('in', true) + var dimensions = utilGetDimensions(tooltip.classed('in', true) .select('.tooltip-inner') - .html(html)); + .html(html)); var side, pos; @@ -88,7 +91,7 @@ export function d3curtain() { if (duration !== 0 || !tooltip.classed(side)) { - tooltip.call(Toggle(true)); + tooltip.call(uiToggle(true)); } tooltip @@ -96,13 +99,14 @@ export function d3curtain() { .style('left', pos[0] + 'px') .attr('class', 'curtain-tooltip tooltip in ' + side + ' ' + tooltipclass) .select('.tooltip-inner') - .html(html); + .html(html); } else { - tooltip.call(Toggle(false)); + tooltip.call(uiToggle(false)); } }; + curtain.cut = function(datum, duration) { darkness.datum(datum); @@ -122,10 +126,12 @@ export function d3curtain() { }); }; + curtain.remove = function() { surface.remove(); tooltip.remove(); }; - return rebind(curtain, dispatch, 'on'); + + return utilRebind(curtain, dispatch, 'on'); } diff --git a/modules/util/detect.js b/modules/util/detect.js index 217678f96..ae6bc58e1 100644 --- a/modules/util/detect.js +++ b/modules/util/detect.js @@ -1,6 +1,6 @@ import { currentLocale } from './locale'; -export function Detect() { +export function utilDetect() { var detected = {}; var ua = navigator.userAgent, diff --git a/modules/util/dimensions.js b/modules/util/dimensions.js index 363ac99a0..34aa656e1 100644 --- a/modules/util/dimensions.js +++ b/modules/util/dimensions.js @@ -5,7 +5,8 @@ function refresh(selection, node) { return prop; } -export function getDimensions (selection) { + +export function utilGetDimensions(selection) { if (!selection || selection.empty()) { return [0, 0]; } @@ -13,7 +14,8 @@ export function getDimensions (selection) { return selection.property('__dimensions__') || refresh(selection, node); } -export function setDimensions (selection, dimensions) { + +export function utilSetDimensions(selection, dimensions) { if (!selection || selection.empty()) { return [0, 0]; } diff --git a/modules/util/get_set_value.js b/modules/util/get_set_value.js index 86da9b47b..e77e84d49 100644 --- a/modules/util/get_set_value.js +++ b/modules/util/get_set_value.js @@ -1,6 +1,6 @@ // Like selection.property('value', ...), but avoids no-op value sets, // which can result in layout/repaint thrashing in some situations. -export function getSetValue(selection, value) { +export function utilGetSetValue(selection, value) { function d3_selection_value(value) { function valueNull() { delete this.value; diff --git a/modules/util/index.js b/modules/util/index.js index d0042c5fb..29faf5449 100644 --- a/modules/util/index.js +++ b/modules/util/index.js @@ -1,21 +1,21 @@ -export { tagText } from './util'; -export { entitySelector } from './util'; -export { entityOrMemberSelector } from './util'; -export { displayName } from './util'; -export { displayType } from './util'; -export { stringQs } from './util'; -export { qsString } from './util'; -export { prefixDOMProperty } from './util'; -export { prefixCSSProperty } from './util'; -export { setTransform } from './util'; -export { getStyle } from './util'; -export { editDistance } from './util'; -export { fastMouse } from './util'; -export { getPrototypeOf } from './util'; -export { asyncMap } from './util'; -export { wrap } from './util'; -export { functor } from './util'; -export { SessionMutex } from './session_mutex'; -export { SuggestNames } from './suggest_names'; -export { triggerEvent } from './trigger_event'; -export { getSetValue } from './get_set_value'; +export { utilTagText } from './util'; +export { utilEntitySelector } from './util'; +export { utilEntityOrMemberSelector } from './util'; +export { utilDisplayName } from './util'; +export { utilDisplayType } from './util'; +export { utilStringQs } from './util'; +export { utilQsString } from './util'; +export { utilPrefixDOMProperty } from './util'; +export { utilPrefixCSSProperty } from './util'; +export { utilSetTransform } from './util'; +export { utilGetStyle } from './util'; +export { utilEditDistance } from './util'; +export { utilFastMouse } from './util'; +export { utilGetPrototypeOf } from './util'; +export { utilAsyncMap } from './util'; +export { utilWrap } from './util'; +export { utilFunctor } from './util'; +export { utilSessionMutex } from './session_mutex'; +export { utilSuggestNames } from './suggest_names'; +export { utilTriggerEvent } from './trigger_event'; +export { utilGetSetValue } from './get_set_value'; diff --git a/modules/util/rebind.js b/modules/util/rebind.js index 32943c146..eead6dd0a 100644 --- a/modules/util/rebind.js +++ b/modules/util/rebind.js @@ -1,5 +1,5 @@ // Copies a variable number of methods from source to target. -export function rebind(target, source) { +export function utilRebind(target, source) { var i = 1, n = arguments.length, method; while (++i < n) { target[method = arguments[i]] = d3_rebind(target, source, source[method]); diff --git a/modules/util/session_mutex.js b/modules/util/session_mutex.js index f71a6e413..472dcd4a3 100644 --- a/modules/util/session_mutex.js +++ b/modules/util/session_mutex.js @@ -2,7 +2,7 @@ // switch. If the session crashes, the mutex will auto-release // after 5 seconds. -export function SessionMutex(name) { +export function utilSessionMutex(name) { var mutex = {}, intervalID; diff --git a/modules/util/suggest_names.js b/modules/util/suggest_names.js index 8de075437..1ced153c6 100644 --- a/modules/util/suggest_names.js +++ b/modules/util/suggest_names.js @@ -1,6 +1,6 @@ -import { editDistance } from './util'; +import { utilEditDistance } from './util'; -export function SuggestNames(preset, suggestions) { +export function utilSuggestNames(preset, suggestions) { preset = preset.id.split('/', 2); var k = preset[0], v = preset[1]; @@ -10,7 +10,7 @@ export function SuggestNames(preset, suggestions) { if (value && value.length > 2) { if (suggestions[k] && suggestions[k][v]) { for (var sugg in suggestions[k][v]) { - var dist = editDistance(value, sugg.substring(0, value.length)); + var dist = utilEditDistance(value, sugg.substring(0, value.length)); if (dist < 3) { result.push({ title: sugg, diff --git a/modules/util/tooltip.js b/modules/util/tooltip.js index 6a0b98c42..1bf8e9555 100644 --- a/modules/util/tooltip.js +++ b/modules/util/tooltip.js @@ -1,12 +1,12 @@ import * as d3 from 'd3'; -import { functor } from './index'; +import { utilFunctor } from './index'; export function tooltip() { var tooltip = function(selection) { selection.each(setup); }, - animation = functor(false), - html = functor(false), + animation = utilFunctor(false), + html = utilFunctor(false), title = function() { var title = this.getAttribute('data-original-title'); if (title) { @@ -20,11 +20,11 @@ export function tooltip() { }, over = 'mouseenter.tooltip', out = 'mouseleave.tooltip', - placement = functor('top'); + placement = utilFunctor('top'); tooltip.title = function(_) { if (arguments.length) { - title = functor(_); + title = utilFunctor(_); return tooltip; } else { return title; @@ -33,7 +33,7 @@ export function tooltip() { tooltip.html = function(_) { if (arguments.length) { - html = functor(_); + html = utilFunctor(_); return tooltip; } else { return html; @@ -42,7 +42,7 @@ export function tooltip() { tooltip.placement = function(_) { if (arguments.length) { - placement = functor(_); + placement = utilFunctor(_); return tooltip; } else { return placement; diff --git a/modules/util/trigger_event.js b/modules/util/trigger_event.js index e54b148da..2639d60de 100644 --- a/modules/util/trigger_event.js +++ b/modules/util/trigger_event.js @@ -1,4 +1,4 @@ -export function triggerEvent(target, type) { +export function utilTriggerEvent(target, type) { target.each(function() { var evt = document.createEvent('HTMLEvents'); evt.initEvent(type, true, true); diff --git a/modules/util/util.js b/modules/util/util.js index e6486d02d..42f336eaf 100644 --- a/modules/util/util.js +++ b/modules/util/util.js @@ -1,21 +1,24 @@ import * as d3 from 'd3'; import _ from 'lodash'; import { t } from './locale'; -import { Detect } from './detect'; +import { utilDetect } from './detect'; import { remove as removeDiacritics } from 'diacritics'; -export function tagText(entity) { + +export function utilTagText(entity) { return d3.entries(entity.tags).map(function(e) { return e.key + '=' + e.value; }).join(', '); } -export function entitySelector(ids) { + +export function utilEntitySelector(ids) { return ids.length ? '.' + ids.join(',.') : 'nothing'; } -export function entityOrMemberSelector(ids, graph) { - var s = entitySelector(ids); + +export function utilEntityOrMemberSelector(ids, graph) { + var s = utilEntitySelector(ids); ids.forEach(function(id) { var entity = graph.hasEntity(id); @@ -29,8 +32,9 @@ export function entityOrMemberSelector(ids, graph) { return s; } -export function displayName(entity) { - var localizedNameKey = 'name:' + Detect().locale.toLowerCase().split('-')[0], + +export function utilDisplayName(entity) { + var localizedNameKey = 'name:' + utilDetect().locale.toLowerCase().split('-')[0], name = entity.tags[localizedNameKey] || entity.tags.name || '', network = entity.tags.cycle_network || entity.tags.network; if (!name && entity.tags.ref) { @@ -42,7 +46,8 @@ export function displayName(entity) { return name; } -export function displayType(id) { + +export function utilDisplayType(id) { return { n: t('inspector.node'), w: t('inspector.way'), @@ -50,7 +55,8 @@ export function displayType(id) { }[id.charAt(0)]; } -export function stringQs(str) { + +export function utilStringQs(str) { return str.split('&').reduce(function(obj, pair){ var parts = pair.split('='); if (parts.length === 2) { @@ -60,7 +66,8 @@ export function stringQs(str) { }, {}); } -export function qsString(obj, noencode) { + +export function utilQsString(obj, noencode) { function softEncode(s) { // encode everything except special characters used in certain hash parameters: // "/" in map states, ":", ",", {" and "}" in background @@ -72,7 +79,8 @@ export function qsString(obj, noencode) { }).join('&'); } -export function prefixDOMProperty(property) { + +export function utilPrefixDOMProperty(property) { var prefixes = ['webkit', 'ms', 'moz', 'o'], i = -1, n = prefixes.length, @@ -90,7 +98,8 @@ export function prefixDOMProperty(property) { return false; } -export function prefixCSSProperty(property) { + +export function utilPrefixCSSProperty(property) { var prefixes = ['webkit', 'ms', 'Moz', 'O'], i = -1, n = prefixes.length, @@ -108,7 +117,7 @@ export function prefixCSSProperty(property) { var transformProperty; -export function setTransform(el, x, y, scale) { +export function utilSetTransform(el, x, y, scale) { var prop = transformProperty = transformProperty || prefixCSSProperty('Transform'), translate = Detect().opera ? 'translate(' + x + 'px,' + y + 'px)' : @@ -116,7 +125,8 @@ export function setTransform(el, x, y, scale) { return el.style(prop, translate + (scale ? ' scale(' + scale + ')' : '')); } -export function getStyle(selector) { + +export function utilGetStyle(selector) { for (var i = 0; i < document.styleSheets.length; i++) { var rules = document.styleSheets[i].rules || document.styleSheets[i].cssRules || []; for (var k = 0; k < rules.length; k++) { @@ -128,10 +138,11 @@ export function getStyle(selector) { } } + // Calculates Levenshtein distance between two strings // see: https://en.wikipedia.org/wiki/Levenshtein_distance // first converts the strings to lowercase and replaces diacritic marks with ascii equilivants. -export function editDistance(a, b) { +export function utilEditDistance(a, b) { a = removeDiacritics(a.toLowerCase()); b = removeDiacritics(b.toLowerCase()); if (a.length === 0) return b.length; @@ -153,10 +164,11 @@ export function editDistance(a, b) { return matrix[b.length][a.length]; } + // a d3.mouse-alike which // 1. Only works on HTML elements, not SVG // 2. Does not cause style recalculation -export function fastMouse(container) { +export function utilFastMouse(container) { var rect = container.getBoundingClientRect(), rectLeft = rect.left, rectTop = rect.top, @@ -169,11 +181,13 @@ export function fastMouse(container) { }; } + /* eslint-disable no-proto */ -export var getPrototypeOf = Object.getPrototypeOf || function(obj) { return obj.__proto__; }; +export var utilGetPrototypeOf = Object.getPrototypeOf || function(obj) { return obj.__proto__; }; /* eslint-enable no-proto */ -export function asyncMap(inputs, func, callback) { + +export function utilAsyncMap(inputs, func, callback) { var remaining = inputs.length, results = [], errors = []; @@ -188,20 +202,22 @@ export function asyncMap(inputs, func, callback) { }); } + // wraps an index to an interval [0..length-1] -export function wrap(index, length) { +export function utilWrap(index, length) { if (index < 0) index += Math.ceil(-index/length)*length; return index % length; } + /** * a replacement for functor * * @param {*} value any value * @returns {Function} a function that returns that value or the value if it's a function */ -export function functor(value) { +export function utilFunctor(value) { if (typeof value === 'function') return value; return function() { return value; diff --git a/modules/validations/deprecated_tag.js b/modules/validations/deprecated_tag.js index 52ac36646..7c416a7a0 100644 --- a/modules/validations/deprecated_tag.js +++ b/modules/validations/deprecated_tag.js @@ -1,8 +1,10 @@ import _ from 'lodash'; import { t } from '../util/locale'; -import { tagText } from '../util/index'; +import { utilTagText } from '../util/index'; + + +export function validationDeprecatedTag() { -export function DeprecatedTag() { var validation = function(changes) { var warnings = []; for (var i = 0; i < changes.created.length; i++) { @@ -10,7 +12,7 @@ export function DeprecatedTag() { deprecatedTags = change.deprecatedTags(); if (!_.isEmpty(deprecatedTags)) { - var tags = tagText({ tags: deprecatedTags }); + var tags = utilTagText({ tags: deprecatedTags }); warnings.push({ id: 'deprecated_tags', message: t('validations.deprecated_tags', { tags: tags }), @@ -18,8 +20,10 @@ export function DeprecatedTag() { }); } } + return warnings; }; + return validation; } diff --git a/modules/validations/index.js b/modules/validations/index.js index 1e8966154..c6abfda2a 100644 --- a/modules/validations/index.js +++ b/modules/validations/index.js @@ -1,4 +1,4 @@ -export { DeprecatedTag } from './deprecated_tag'; -export { ManyDeletions } from './many_deletions'; -export { MissingTag } from './missing_tag'; -export { TagSuggestsArea } from './tag_suggests_area'; +export { validationDeprecatedTag } from './deprecated_tag'; +export { validationManyDeletions } from './many_deletions'; +export { validationMissingTag } from './missing_tag'; +export { validationTagSuggestsArea } from './tag_suggests_area'; diff --git a/modules/validations/many_deletions.js b/modules/validations/many_deletions.js index bf74973cf..665d81e57 100644 --- a/modules/validations/many_deletions.js +++ b/modules/validations/many_deletions.js @@ -1,6 +1,6 @@ import { t } from '../util/locale'; -export function ManyDeletions() { +export function validationManyDeletions() { var threshold = 100; var validation = function(changes) { @@ -11,8 +11,10 @@ export function ManyDeletions() { message: t('validations.many_deletions', { n: changes.deleted.length }) }); } + return warnings; }; + return validation; } diff --git a/modules/validations/missing_tag.js b/modules/validations/missing_tag.js index f5255ac1e..5d3f01185 100644 --- a/modules/validations/missing_tag.js +++ b/modules/validations/missing_tag.js @@ -1,7 +1,8 @@ import _ from 'lodash'; import { t } from '../util/locale'; -export function MissingTag() { +export function validationMissingTag() { + // Slightly stricter check than Entity#isUsed (#3091) function hasTags(entity, graph) { return _.without(Object.keys(entity.tags), 'area', 'name').length > 0 || @@ -23,8 +24,10 @@ export function MissingTag() { }); } } + return warnings; }; + return validation; } diff --git a/modules/validations/tag_suggests_area.js b/modules/validations/tag_suggests_area.js index a4fba7227..1a85815d5 100644 --- a/modules/validations/tag_suggests_area.js +++ b/modules/validations/tag_suggests_area.js @@ -1,9 +1,11 @@ import _ from 'lodash'; import { t } from '../util/locale'; + // https://github.com/openstreetmap/josm/blob/mirror/src/org/ // openstreetmap/josm/data/validation/tests/UnclosedWays.java#L80 -export function TagSuggestsArea() { +export function validationTagSuggestsArea() { + function tagSuggestsArea(tags) { if (_.isEmpty(tags)) return false; @@ -17,6 +19,7 @@ export function TagSuggestsArea() { if (tags.building && tags.building === 'yes') return 'building=yes'; } + var validation = function(changes, graph) { var warnings = []; for (var i = 0; i < changes.created.length; i++) { @@ -32,8 +35,10 @@ export function TagSuggestsArea() { }); } } + return warnings; }; + return validation; }