From 33b2b1e6fd2abda1a0f4d88a9b21fe2bd1a8e9c6 Mon Sep 17 00:00:00 2001 From: Kushan Joshi <0o3ko0@gmail.com> Date: Sat, 18 Jun 2016 15:52:39 +0530 Subject: [PATCH] external modules for modes --- Makefile | 1 - index.html | 1 - js/lib/id/index.js | 2351 ++++++++++++++++++++++++++++++++++- modules/index.js | 4 +- modules/modes/add_area.js | 46 +- modules/modes/add_line.js | 40 +- modules/modes/add_point.js | 14 +- modules/modes/browse.js | 12 +- modules/modes/drag_node.js | 40 +- modules/modes/draw_area.js | 3 +- modules/modes/draw_line.js | 3 +- modules/modes/move.js | 19 +- modules/modes/rotate_way.js | 15 +- modules/modes/save.js | 36 +- modules/modes/select.js | 51 +- test/index.html | 1 - 16 files changed, 2479 insertions(+), 158 deletions(-) diff --git a/Makefile b/Makefile index 86e0193e5..3d58b4244 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,6 @@ $(BUILDJS_TARGETS): $(BUILDJS_SOURCES) build.js MODULE_TARGETS = \ js/lib/id/index.js \ - js/lib/id/modes.js \ js/lib/id/operations.js \ js/lib/id/presets.js \ js/lib/id/renderer.js \ diff --git a/index.html b/index.html index e92f24a14..83365f505 100644 --- a/index.html +++ b/index.html @@ -37,7 +37,6 @@ - diff --git a/js/lib/id/index.js b/js/lib/id/index.js index 0b57ac0c7..bf3ff7455 100644 --- a/js/lib/id/index.js +++ b/js/lib/id/index.js @@ -1577,11 +1577,34 @@ return ids.length ? '.' + ids.join(',.') : 'nothing'; } + function entityOrMemberSelector(ids, graph) { + var s = entitySelector(ids); + + ids.forEach(function(id) { + var entity = graph.hasEntity(id); + if (entity && entity.type === 'relation') { + entity.members.forEach(function(member) { + s += ',.' + member.id; + }); + } + }); + + return s; + } + function displayName(entity) { var localeName = 'name:' + iD.detect().locale.toLowerCase().split('-')[0]; return entity.tags[localeName] || entity.tags.name || entity.tags.ref; } + function displayType(id) { + return { + n: t('inspector.node'), + w: t('inspector.way'), + r: t('inspector.relation') + }[id.charAt(0)]; + } + function stringQs(str) { return str.split('&').reduce(function(obj, pair){ var parts = pair.split('='); @@ -2120,6 +2143,285 @@ return result; } + function Commit(context) { + var dispatch = d3.dispatch('cancel', 'save'); + + function commit(selection) { + var changes = context.history().changes(), + summary = context.history().difference().summary(); + + function zoomToEntity(change) { + var entity = change.entity; + if (change.changeType !== 'deleted' && + context.graph().entity(entity.id).geometry(context.graph()) !== 'vertex') { + context.map().zoomTo(entity); + context.surface().selectAll( + iD.util.entityOrMemberSelector([entity.id], context.graph())) + .classed('hover', true); + } + } + + var header = selection.append('div') + .attr('class', 'header fillL'); + + header.append('h3') + .text(t('commit.title')); + + var body = selection.append('div') + .attr('class', 'body'); + + + // Comment Section + var commentSection = body.append('div') + .attr('class', 'modal-section form-field commit-form'); + + commentSection.append('label') + .attr('class', 'form-label') + .text(t('commit.message_label')); + + var commentField = commentSection.append('textarea') + .attr('placeholder', t('commit.description_placeholder')) + .attr('maxlength', 255) + .property('value', context.storage('comment') || '') + .on('input.save', checkComment) + .on('change.save', checkComment) + .on('blur.save', function() { + context.storage('comment', this.value); + }); + + function checkComment() { + d3.selectAll('.save-section .save-button') + .attr('disabled', (this.value.length ? null : true)); + + var googleWarning = clippyArea + .html('') + .selectAll('a') + .data(this.value.match(/google/i) ? [true] : []); + + googleWarning.exit().remove(); + + googleWarning.enter() + .append('a') + .attr('target', '_blank') + .attr('tabindex', -1) + .call(iD.svg.Icon('#icon-alert', 'inline')) + .attr('href', t('commit.google_warning_link')) + .append('span') + .text(t('commit.google_warning')); + } + + commentField.node().select(); + + context.connection().userChangesets(function (err, changesets) { + if (err) return; + + var comments = []; + + for (var i = 0; i < changesets.length; i++) { + if (changesets[i].tags.comment) { + comments.push({ + title: changesets[i].tags.comment, + value: changesets[i].tags.comment + }); + } + } + + commentField.call(d3.combobox().caseSensitive(true).data(comments)); + }); + + var clippyArea = commentSection.append('div') + .attr('class', 'clippy-area'); + + + var changeSetInfo = commentSection.append('div') + .attr('class', 'changeset-info'); + + changeSetInfo.append('a') + .attr('target', '_blank') + .attr('tabindex', -1) + .call(iD.svg.Icon('#icon-out-link', 'inline')) + .attr('href', t('commit.about_changeset_comments_link')) + .append('span') + .text(t('commit.about_changeset_comments')); + + // Warnings + var warnings = body.selectAll('div.warning-section') + .data([context.history().validate(changes)]) + .enter() + .append('div') + .attr('class', 'modal-section warning-section fillL2') + .style('display', function(d) { return _.isEmpty(d) ? 'none' : null; }) + .style('background', '#ffb'); + + warnings.append('h3') + .text(t('commit.warnings')); + + var warningLi = warnings.append('ul') + .attr('class', 'changeset-list') + .selectAll('li') + .data(function(d) { return d; }) + .enter() + .append('li') + .style() + .on('mouseover', mouseover) + .on('mouseout', mouseout) + .on('click', warningClick); + + warningLi + .call(iD.svg.Icon('#icon-alert', 'pre-text')); + + warningLi + .append('strong').text(function(d) { + return d.message; + }); + + warningLi.filter(function(d) { return d.tooltip; }) + .call(bootstrap.tooltip() + .title(function(d) { return d.tooltip; }) + .placement('top') + ); + + + // Upload Explanation + var saveSection = body.append('div') + .attr('class','modal-section save-section fillL cf'); + + var prose = saveSection.append('p') + .attr('class', 'commit-info') + .html(t('commit.upload_explanation')); + + context.connection().userDetails(function(err, user) { + if (err) return; + + var userLink = d3.select(document.createElement('div')); + + if (user.image_url) { + userLink.append('img') + .attr('src', user.image_url) + .attr('class', 'icon pre-text user-icon'); + } + + userLink.append('a') + .attr('class','user-info') + .text(user.display_name) + .attr('href', context.connection().userURL(user.display_name)) + .attr('tabindex', -1) + .attr('target', '_blank'); + + prose.html(t('commit.upload_explanation_with_user', {user: userLink.html()})); + }); + + + // Buttons + var buttonSection = saveSection.append('div') + .attr('class','buttons fillL cf'); + + var cancelButton = buttonSection.append('button') + .attr('class', 'secondary-action col5 button cancel-button') + .on('click.cancel', function() { dispatch.cancel(); }); + + cancelButton.append('span') + .attr('class', 'label') + .text(t('commit.cancel')); + + var saveButton = buttonSection.append('button') + .attr('class', 'action col5 button save-button') + .attr('disabled', function() { + var n = d3.select('.commit-form textarea').node(); + return (n && n.value.length) ? null : true; + }) + .on('click.save', function() { + dispatch.save({ + comment: commentField.node().value + }); + }); + + saveButton.append('span') + .attr('class', 'label') + .text(t('commit.save')); + + + // Changes + var changeSection = body.selectAll('div.commit-section') + .data([0]) + .enter() + .append('div') + .attr('class', 'commit-section modal-section fillL2'); + + changeSection.append('h3') + .text(t('commit.changes', {count: summary.length})); + + var li = changeSection.append('ul') + .attr('class', 'changeset-list') + .selectAll('li') + .data(summary) + .enter() + .append('li') + .on('mouseover', mouseover) + .on('mouseout', mouseout) + .on('click', zoomToEntity); + + li.each(function(d) { + d3.select(this) + .call(iD.svg.Icon('#icon-' + d.entity.geometry(d.graph), 'pre-text ' + d.changeType)); + }); + + li.append('span') + .attr('class', 'change-type') + .text(function(d) { + return t('commit.' + d.changeType) + ' '; + }); + + li.append('strong') + .attr('class', 'entity-type') + .text(function(d) { + return context.presets().match(d.entity, d.graph).name(); + }); + + li.append('span') + .attr('class', 'entity-name') + .text(function(d) { + var name = iD.util.displayName(d.entity) || '', + string = ''; + if (name !== '') string += ':'; + return string += ' ' + name; + }); + + li.style('opacity', 0) + .transition() + .style('opacity', 1); + + + function mouseover(d) { + if (d.entity) { + context.surface().selectAll( + iD.util.entityOrMemberSelector([d.entity.id], context.graph()) + ).classed('hover', true); + } + } + + function mouseout() { + context.surface().selectAll('.hover') + .classed('hover', false); + } + + function warningClick(d) { + if (d.entity) { + context.map().zoomTo(d.entity); + context.enter( + iD.modes.Select(context, [d.entity.id]) + .suppressMenu(true)); + } + } + + // Call checkComment off the bat, in case a changeset + // comment is recovered from localStorage + commentField.trigger('input'); + } + + return d3.rebind(commit, dispatch, 'on'); + } + function modalModule(selection, blocking) { var keybinding = d3.keybinding('modal'); var previous = selection.select('div.modal'); @@ -2184,6 +2486,257 @@ return shaded; } + function Conflicts(context) { + var dispatch = d3.dispatch('download', 'cancel', 'save'), + list; + + function conflicts(selection) { + var header = selection + .append('div') + .attr('class', 'header fillL'); + + header + .append('button') + .attr('class', 'fr') + .on('click', function() { dispatch.cancel(); }) + .call(iD.svg.Icon('#icon-close')); + + header + .append('h3') + .text(t('save.conflict.header')); + + var body = selection + .append('div') + .attr('class', 'body fillL'); + + body + .append('div') + .attr('class', 'conflicts-help') + .text(t('save.conflict.help')) + .append('a') + .attr('class', 'conflicts-download') + .text(t('save.conflict.download_changes')) + .on('click.download', function() { dispatch.download(); }); + + body + .append('div') + .attr('class', 'conflict-container fillL3') + .call(showConflict, 0); + + body + .append('div') + .attr('class', 'conflicts-done') + .attr('opacity', 0) + .style('display', 'none') + .text(t('save.conflict.done')); + + var buttons = body + .append('div') + .attr('class','buttons col12 joined conflicts-buttons'); + + buttons + .append('button') + .attr('disabled', list.length > 1) + .attr('class', 'action conflicts-button col6') + .text(t('save.title')) + .on('click.try_again', function() { dispatch.save(); }); + + buttons + .append('button') + .attr('class', 'secondary-action conflicts-button col6') + .text(t('confirm.cancel')) + .on('click.cancel', function() { dispatch.cancel(); }); + } + + + function showConflict(selection, index) { + if (index < 0 || index >= list.length) return; + + var parent = d3.select(selection.node().parentNode); + + // enable save button if this is the last conflict being reviewed.. + if (index === list.length - 1) { + window.setTimeout(function() { + parent.select('.conflicts-button') + .attr('disabled', null); + + parent.select('.conflicts-done') + .transition() + .attr('opacity', 1) + .style('display', 'block'); + }, 250); + } + + var item = selection + .selectAll('.conflict') + .data([list[index]]); + + var enter = item.enter() + .append('div') + .attr('class', 'conflict'); + + enter + .append('h4') + .attr('class', 'conflict-count') + .text(t('save.conflict.count', { num: index + 1, total: list.length })); + + enter + .append('a') + .attr('class', 'conflict-description') + .attr('href', '#') + .text(function(d) { return d.name; }) + .on('click', function(d) { + zoomToEntity(d.id); + d3.event.preventDefault(); + }); + + var details = enter + .append('div') + .attr('class', 'conflict-detail-container'); + + details + .append('ul') + .attr('class', 'conflict-detail-list') + .selectAll('li') + .data(function(d) { return d.details || []; }) + .enter() + .append('li') + .attr('class', 'conflict-detail-item') + .html(function(d) { return d; }); + + details + .append('div') + .attr('class', 'conflict-choices') + .call(addChoices); + + details + .append('div') + .attr('class', 'conflict-nav-buttons joined cf') + .selectAll('button') + .data(['previous', 'next']) + .enter() + .append('button') + .text(function(d) { return t('save.conflict.' + d); }) + .attr('class', 'conflict-nav-button action col6') + .attr('disabled', function(d, i) { + return (i === 0 && index === 0) || + (i === 1 && index === list.length - 1) || null; + }) + .on('click', function(d, i) { + var container = parent.select('.conflict-container'), + sign = (i === 0 ? -1 : 1); + + container + .selectAll('.conflict') + .remove(); + + container + .call(showConflict, index + sign); + + d3.event.preventDefault(); + }); + + item.exit() + .remove(); + + } + + function addChoices(selection) { + var choices = selection + .append('ul') + .attr('class', 'layer-list') + .selectAll('li') + .data(function(d) { return d.choices || []; }); + + var enter = choices.enter() + .append('li') + .attr('class', 'layer'); + + var label = enter + .append('label'); + + label + .append('input') + .attr('type', 'radio') + .attr('name', function(d) { return d.id; }) + .on('change', function(d, i) { + var ul = this.parentNode.parentNode.parentNode; + ul.__data__.chosen = i; + choose(ul, d); + }); + + label + .append('span') + .text(function(d) { return d.text; }); + + choices + .each(function(d, i) { + var ul = this.parentNode; + if (ul.__data__.chosen === i) choose(ul, d); + }); + } + + function choose(ul, datum) { + if (d3.event) d3.event.preventDefault(); + + d3.select(ul) + .selectAll('li') + .classed('active', function(d) { return d === datum; }) + .selectAll('input') + .property('checked', function(d) { return d === datum; }); + + var extent = iD.geo.Extent(), + entity; + + entity = context.graph().hasEntity(datum.id); + if (entity) extent._extend(entity.extent(context.graph())); + + datum.action(); + + entity = context.graph().hasEntity(datum.id); + if (entity) extent._extend(entity.extent(context.graph())); + + zoomToEntity(datum.id, extent); + } + + function zoomToEntity(id, extent) { + context.surface().selectAll('.hover') + .classed('hover', false); + + var entity = context.graph().hasEntity(id); + if (entity) { + if (extent) { + context.map().trimmedExtent(extent); + } else { + context.map().zoomTo(entity); + } + context.surface().selectAll( + iD.util.entityOrMemberSelector([entity.id], context.graph())) + .classed('hover', true); + } + } + + + // The conflict list should be an array of objects like: + // { + // id: id, + // name: entityName(local), + // details: merge.conflicts(), + // chosen: 1, + // choices: [ + // choice(id, keepMine, forceLocal), + // choice(id, keepTheirs, forceRemote) + // ] + // } + conflicts.list = function(_) { + if (!arguments.length) return list; + list = _; + return conflicts; + }; + + return d3.rebind(conflicts, dispatch, 'on'); + } + // toggles the visibility of ui elements, using a combination of the // hide class, which sets display=none, and a d3 transition for opacity. // this will cause blinking when called repeatedly, so check that the @@ -2300,6 +2853,281 @@ return lasso; } + function RadialMenu(context, operations) { + var menu, + center = [0, 0], + tooltip; + + var radialMenu = function(selection) { + if (!operations.length) + return; + + selection.node().parentNode.focus(); + + function click(operation) { + d3.event.stopPropagation(); + if (operation.disabled()) + return; + operation(); + radialMenu.close(); + } + + menu = selection.append('g') + .attr('class', 'radial-menu') + .attr('transform', 'translate(' + center + ')') + .attr('opacity', 0); + + menu.transition() + .attr('opacity', 1); + + var r = 50, + a = Math.PI / 4, + a0 = -Math.PI / 4, + a1 = a0 + (operations.length - 1) * a; + + menu.append('path') + .attr('class', 'radial-menu-background') + .attr('d', 'M' + r * Math.sin(a0) + ',' + + r * Math.cos(a0) + + ' A' + r + ',' + r + ' 0 ' + (operations.length > 5 ? '1' : '0') + ',0 ' + + (r * Math.sin(a1) + 1e-3) + ',' + + (r * Math.cos(a1) + 1e-3)) // Force positive-length path (#1305) + .attr('stroke-width', 50) + .attr('stroke-linecap', 'round'); + + var button = menu.selectAll() + .data(operations) + .enter() + .append('g') + .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(' + iD.geo.roundCoords([ + r * Math.sin(a0 + i * a), + r * Math.cos(a0 + i * a)]).join(',') + ')'; + }); + + button.append('circle') + .attr('r', 15) + .on('click', click) + .on('mousedown', mousedown) + .on('mouseover', mouseover) + .on('mouseout', mouseout); + + button.append('use') + .attr('transform', 'translate(-10,-10)') + .attr('width', '20') + .attr('height', '20') + .attr('xlink:href', function(d) { return '#operation-' + d.id; }); + + tooltip = d3.select(document.body) + .append('div') + .attr('class', 'tooltip-inner radial-menu-tooltip'); + + function mousedown() { + d3.event.stopPropagation(); // https://github.com/openstreetmap/iD/issues/1869 + } + + function mouseover(d, i) { + var rect = context.surfaceRect(), + angle = a0 + i * a, + top = rect.top + (r + 25) * Math.cos(angle) + center[1] + 'px', + left = rect.left + (r + 25) * Math.sin(angle) + center[0] + 'px', + bottom = rect.height - (r + 25) * Math.cos(angle) - center[1] + 'px', + right = rect.width - (r + 25) * Math.sin(angle) - center[0] + 'px'; + + tooltip + .style('top', null) + .style('left', null) + .style('bottom', null) + .style('right', null) + .style('display', 'block') + .html(iD.ui.tooltipHtml(d.tooltip(), d.keys[0])); + + if (i === 0) { + tooltip + .style('right', right) + .style('top', top); + } else if (i >= 4) { + tooltip + .style('left', left) + .style('bottom', bottom); + } else { + tooltip + .style('left', left) + .style('top', top); + } + } + + function mouseout() { + tooltip.style('display', 'none'); + } + }; + + radialMenu.close = function() { + if (menu) { + menu + .style('pointer-events', 'none') + .transition() + .attr('opacity', 0) + .remove(); + } + + if (tooltip) { + tooltip.remove(); + } + }; + + radialMenu.center = function(_) { + if (!arguments.length) return center; + center = _; + return radialMenu; + }; + + return radialMenu; + } + + function SelectionList(context, selectedIDs) { + + function selectEntity(entity) { + context.enter(iD.modes.Select(context, [entity.id]).suppressMenu(true)); + } + + + function selectionList(selection) { + selection.classed('selection-list-pane', true); + + var header = selection.append('div') + .attr('class', 'header fillL cf'); + + header.append('h3') + .text(t('inspector.multiselect')); + + var listWrap = selection.append('div') + .attr('class', 'inspector-body'); + + var list = listWrap.append('div') + .attr('class', 'feature-list cf'); + + context.history().on('change.selection-list', drawList); + drawList(); + + function drawList() { + var entities = selectedIDs + .map(function(id) { return context.hasEntity(id); }) + .filter(function(entity) { return entity; }); + + var items = list.selectAll('.feature-list-item') + .data(entities, iD.Entity.key); + + var enter = items.enter().append('button') + .attr('class', 'feature-list-item') + .on('click', selectEntity); + + // Enter + var label = enter.append('div') + .attr('class', 'label') + .call(iD.svg.Icon('', 'pre-text')); + + label.append('span') + .attr('class', 'entity-type'); + + label.append('span') + .attr('class', 'entity-name'); + + // Update + items.selectAll('use') + .attr('href', function() { + var entity = this.parentNode.parentNode.__data__; + return '#icon-' + context.geometry(entity.id); + }); + + items.selectAll('.entity-type') + .text(function(entity) { return context.presets().match(entity, context.graph()).name(); }); + + items.selectAll('.entity-name') + .text(function(entity) { return iD.util.displayName(entity); }); + + // Exit + items.exit() + .remove(); + } + } + + return selectionList; + + } + + function Success(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') + .attr('class', 'header fillL'); + + header.append('button') + .attr('class', 'fr') + .on('click', function() { dispatch.cancel(); }) + .call(iD.svg.Icon('#icon-close')); + + header.append('h3') + .text(t('success.just_edited')); + + var body = selection.append('div') + .attr('class', 'body save-success fillL'); + + body.append('p') + .html(t('success.help_html')); + + body.append('a') + .attr('class', 'details') + .attr('target', '_blank') + .attr('tabindex', -1) + .call(iD.svg.Icon('#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') + .attr('class', 'button col12 osm') + .attr('target', '_blank') + .attr('href', changesetURL) + .text(t('success.view_on_osm')); + + var sharing = { + facebook: 'https://facebook.com/sharer/sharer.php?u=' + encodeURIComponent(changesetURL), + twitter: 'https://twitter.com/intent/tweet?source=webclient&text=' + encodeURIComponent(message), + google: 'https://plus.google.com/share?url=' + encodeURIComponent(changesetURL) + }; + + body.selectAll('.button.social') + .data(d3.entries(sharing)) + .enter() + .append('a') + .attr('class', 'button social col4') + .attr('target', '_blank') + .attr('href', function(d) { return d.value; }) + .call(bootstrap.tooltip() + .title(function(d) { return t('success.' + d.key); }) + .placement('bottom')) + .each(function(d) { d3.select(this).call(iD.svg.Icon('#logo-' + d.key, 'social')); }); + } + + success.changeset = function(_) { + if (!arguments.length) return changeset; + changeset = _; + return success; + }; + + return d3.rebind(success, dispatch, 'on'); + } + function History(context) { var stack, index, tree, imageryUsed = ['Bing'], @@ -5372,7 +6200,7 @@ return action; } - function RotateWay(wayId, pivot, angle, projection) { + function RotateWayAction(wayId, pivot, angle, projection) { return function(graph) { return graph.update(function(graph) { var way = graph.entity(wayId); @@ -5534,7 +6362,7 @@ RestrictTurn: RestrictTurn, Reverse: Reverse, Revert: Revert, - RotateWay: RotateWay, + RotateWay: RotateWayAction, Split: Split, Straighten: Straighten, UnrestrictTurn: UnrestrictTurn @@ -8160,6 +8988,191 @@ Vertices: Vertices }); + function AddArea(context) { + var mode = { + id: 'add-area', + button: 'area', + title: t('modes.add_area.title'), + description: t('modes.add_area.description'), + key: '3' + }; + + var behavior = AddWay(context) + .tail(t('modes.add_area.tail')) + .on('start', start) + .on('startFromWay', startFromWay) + .on('startFromNode', startFromNode), + defaultTags = {area: 'yes'}; + + function start(loc) { + var graph = context.graph(), + node = Node({loc: loc}), + way = Way({tags: defaultTags}); + + context.perform( + AddEntity(node), + AddEntity(way), + AddVertex(way.id, node.id), + AddVertex(way.id, node.id)); + + context.enter(DrawArea(context, way.id, graph)); + } + + function startFromWay(loc, edge) { + var graph = context.graph(), + node = Node({loc: loc}), + way = Way({tags: defaultTags}); + + context.perform( + AddEntity(node), + AddEntity(way), + AddVertex(way.id, node.id), + AddVertex(way.id, node.id), + AddMidpoint({ loc: loc, edge: edge }, node)); + + context.enter(DrawArea(context, way.id, graph)); + } + + function startFromNode(node) { + var graph = context.graph(), + way = Way({tags: defaultTags}); + + context.perform( + AddEntity(way), + AddVertex(way.id, node.id), + AddVertex(way.id, node.id)); + + context.enter(DrawArea(context, way.id, graph)); + } + + mode.enter = function() { + context.install(behavior); + }; + + mode.exit = function() { + context.uninstall(behavior); + }; + + return mode; + } + + function AddLine(context) { + var mode = { + id: 'add-line', + button: 'line', + title: t('modes.add_line.title'), + description: t('modes.add_line.description'), + key: '2' + }; + + var behavior = AddWay(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(); + + context.perform( + AddEntity(node), + AddEntity(way), + AddVertex(way.id, node.id)); + + context.enter(DrawLine(context, way.id, baseGraph)); + } + + function startFromWay(loc, edge) { + var baseGraph = context.graph(), + node = Node({loc: loc}), + way = Way(); + + context.perform( + AddEntity(node), + AddEntity(way), + AddVertex(way.id, node.id), + AddMidpoint({ loc: loc, edge: edge }, node)); + + context.enter(DrawLine(context, way.id, baseGraph)); + } + + function startFromNode(node) { + var baseGraph = context.graph(), + way = Way(); + + context.perform( + AddEntity(way), + AddVertex(way.id, node.id)); + + context.enter(DrawLine(context, way.id, baseGraph)); + } + + mode.enter = function() { + context.install(behavior); + }; + + mode.exit = function() { + context.uninstall(behavior); + }; + + return mode; + } + + function AddPoint(context) { + var mode = { + id: 'add-point', + button: 'point', + title: t('modes.add_point.title'), + description: t('modes.add_point.description'), + key: '1' + }; + + var behavior = Draw(context) + .tail(t('modes.add_point.tail')) + .on('click', add) + .on('clickWay', addWay) + .on('clickNode', addNode) + .on('cancel', cancel) + .on('finish', cancel); + + function add(loc) { + var node = Node({loc: loc}); + + context.perform( + AddEntity(node), + t('operations.add.annotation.point')); + + context.enter( + SelectMode(context, [node.id]) + .suppressMenu(true) + .newFeature(true)); + } + + function addWay(loc) { + add(loc); + } + + function addNode(node) { + add(node.loc); + } + + function cancel() { + context.enter(Browse(context)); + } + + mode.enter = function() { + context.install(behavior); + }; + + mode.exit = function() { + context.uninstall(behavior); + }; + + return mode; + } + function Browse(context) { var mode = { button: 'browse', @@ -8169,12 +9182,12 @@ }, sidebar; var behaviors = [ - iD.behavior.Paste(context), - iD.behavior.Hover(context) + Paste(context), + Hover(context) .on('hover', context.ui().sidebar.hover), - iD.behavior.Select(context), - iD.behavior.Lasso(context), - iD.modes.DragNode(context).behavior]; + Select(context), + Lasso(context), + DragNode(context).behavior]; mode.enter = function() { behaviors.forEach(function(behavior) { @@ -8213,6 +9226,302 @@ return mode; } + function DragNode(context) { + var mode = { + id: 'drag-node', + button: 'browse' + }; + + var nudgeInterval, + activeIDs, + wasMidpoint, + cancelled, + selectedIDs = [], + hover = Hover(context) + .altDisables(true) + .on('hover', context.ui().sidebar.hover), + edit = Edit(context); + + function edge(point, size) { + var pad = [30, 100, 30, 100]; + if (point[0] > size[0] - pad[0]) return [-10, 0]; + else if (point[0] < pad[2]) return [10, 0]; + else if (point[1] > size[1] - pad[1]) return [0, -10]; + else if (point[1] < pad[3]) return [0, 10]; + return null; + } + + function startNudge(nudge) { + if (nudgeInterval) window.clearInterval(nudgeInterval); + nudgeInterval = window.setInterval(function() { + context.pan(nudge); + }, 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 || + context.features().hasHiddenConnections(entity, context.graph()); + + if (cancelled) return behavior.cancel(); + + wasMidpoint = entity.type === 'midpoint'; + if (wasMidpoint) { + var midpoint = entity; + entity = Node(); + context.perform(AddMidpoint(midpoint, entity)); + + var vertex = context.surface() + .selectAll('.' + entity.id); + behavior.target(vertex.node(), entity); + + } else { + context.perform( + Noop()); + } + + activeIDs = _.map(context.graph().parentWays(entity), 'id'); + activeIDs.push(entity.id); + + context.enter(mode); + } + + function datum() { + if (d3.event.sourceEvent.altKey) { + return {}; + } + + return d3.event.sourceEvent.target.__data__ || {}; + } + + // via https://gist.github.com/shawnbot/4166283 + function childOf(p, c) { + if (p === c) return false; + while (c && c !== p) c = c.parentNode; + return c === p; + } + + function move(entity) { + if (cancelled) return; + d3.event.sourceEvent.stopPropagation(); + + var nudge = childOf(context.container().node(), + d3.event.sourceEvent.toElement) && + edge(d3.event.point, context.map().dimensions()); + + if (nudge) startNudge(nudge); + else stopNudge(); + + var loc = context.projection.invert(d3.event.point); + + var d = datum(); + 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; + } + + context.replace( + MoveNode(entity.id, loc), + moveAnnotation(entity)); + } + + function end(entity) { + if (cancelled) return; + + var d = datum(); + + if (d.type === 'way') { + var choice = chooseEdge(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)); + + } else if (d.type === 'node' && d.id !== entity.id) { + context.replace( + Connect([d.id, entity.id]), + connectAnnotation(d)); + + } else if (wasMidpoint) { + context.replace( + Noop(), + t('operations.add.annotation.vertex')); + + } else { + context.replace( + Noop(), + moveAnnotation(entity)); + } + + var reselection = selectedIDs.filter(function(id) { + return context.graph().hasEntity(id); + }); + + if (reselection.length) { + context.enter( + SelectMode(context, reselection) + .suppressMenu(true)); + } else { + context.enter(Browse(context)); + } + } + + function cancel() { + behavior.cancel(); + context.enter(Browse(context)); + } + + function setActiveElements() { + context.surface().selectAll(entitySelector(activeIDs)) + .classed('active', true); + } + + var behavior = drag() + .delegate('g.node, g.point, g.midpoint') + .surface(context.surface().node()) + .origin(origin) + .on('start', start) + .on('move', move) + .on('end', end); + + mode.enter = function() { + context.install(hover); + context.install(edit); + + context.history() + .on('undone.drag-node', cancel); + + context.map() + .on('drawn.drag-node', setActiveElements); + + setActiveElements(); + }; + + mode.exit = function() { + context.ui().sidebar.hover.cancel(); + context.uninstall(hover); + context.uninstall(edit); + + context.history() + .on('undone.drag-node', null); + + context.map() + .on('drawn.drag-node', null); + + context.surface() + .selectAll('.active') + .classed('active', false); + + stopNudge(); + }; + + mode.selectedIDs = function(_) { + if (!arguments.length) return selectedIDs; + selectedIDs = _; + return mode; + }; + + mode.behavior = behavior; + + return mode; + } + + function DrawArea(context, wayId, baseGraph) { + var mode = { + button: 'area', + id: 'draw-area' + }; + + 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) + .tail(t('modes.draw_area.tail')); + + var addNode = behavior.addNode; + + behavior.addNode = function(node) { + if (node.id === headId || node.id === tailId) { + behavior.finish(); + } else { + addNode(node); + } + }; + + context.install(behavior); + }; + + mode.exit = function() { + context.uninstall(behavior); + }; + + mode.selectedIDs = function() { + return [wayId]; + }; + + return mode; + } + + function DrawLine(context, wayId, baseGraph, affix) { + var mode = { + button: 'line', + id: 'draw-line' + }; + + 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) + .tail(t('modes.draw_line.tail')); + + var addNode = behavior.addNode; + + behavior.addNode = function(node) { + if (node.id === headId) { + behavior.finish(); + } else { + addNode(node); + } + }; + + context.install(behavior); + }; + + mode.exit = function() { + context.uninstall(behavior); + }; + + mode.selectedIDs = function() { + return [wayId]; + }; + + return mode; + } + function MoveMode(context, entityIDs, baseGraph) { var mode = { id: 'move', @@ -8220,7 +9529,7 @@ }; var keybinding = d3.keybinding('move'), - edit = iD.behavior.Edit(context), + edit = Edit(context), annotation = entityIDs.length === 1 ? t('operations.move.annotation.' + context.geometry(entityIDs[0])) : t('operations.move.annotation.multiple'), @@ -8247,7 +9556,7 @@ var currMouse = context.mouse(), origMouse = context.projection(origin), delta = vecSub(vecSub(currMouse, origMouse), nudge), - action = iD.actions.Move(entityIDs, delta, context.projection, cache); + action = MoveAction(entityIDs, delta, context.projection, cache); context.overwrite(action, annotation); @@ -8263,7 +9572,7 @@ var currMouse = context.mouse(), origMouse = context.projection(origin), delta = vecSub(currMouse, origMouse), - action = iD.actions.Move(entityIDs, delta, context.projection, cache); + action = MoveAction(entityIDs, delta, context.projection, cache); context.overwrite(action, annotation); @@ -8274,23 +9583,23 @@ function finish() { d3.event.stopPropagation(); - context.enter(iD.modes.Select(context, entityIDs).suppressMenu(true)); + context.enter(SelectMode(context, entityIDs).suppressMenu(true)); stopNudge(); } function cancel() { if (baseGraph) { while (context.graph() !== baseGraph) context.pop(); - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } else { context.pop(); - context.enter(iD.modes.Select(context, entityIDs).suppressMenu(true)); + context.enter(SelectMode(context, entityIDs).suppressMenu(true)); } stopNudge(); } function undone() { - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } mode.enter = function() { @@ -8300,7 +9609,7 @@ context.install(edit); context.perform( - iD.actions.Noop(), + Noop(), annotation); context.surface() @@ -8336,6 +9645,956 @@ return mode; } + function RotateWay(context, wayId) { + var mode = { + id: 'rotate-way', + button: 'browse' + }; + + var keybinding = d3.keybinding('rotate-way'), + edit = Edit(context); + + mode.enter = function() { + context.install(edit); + + var annotation = t('operations.rotate.annotation.' + context.geometry(wayId)), + way = context.graph().entity(wayId), + nodes = _.uniq(context.graph().childNodes(way)), + points = nodes.map(function(n) { return context.projection(n.loc); }), + pivot = d3.geom.polygon(points).centroid(), + 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(SelectMode(context, [wayId]) + .suppressMenu(true)); + } + + function cancel() { + context.pop(); + context.enter(SelectMode(context, [wayId]) + .suppressMenu(true)); + } + + function undone() { + context.enter(Browse(context)); + } + + context.surface() + .on('mousemove.rotate-way', rotate) + .on('click.rotate-way', finish); + + context.history() + .on('undone.rotate-way', undone); + + keybinding + .on('⎋', cancel) + .on('↩', finish); + + d3.select(document) + .call(keybinding); + }; + + mode.exit = function() { + context.uninstall(edit); + + context.surface() + .on('mousemove.rotate-way', null) + .on('click.rotate-way', null); + + context.history() + .on('undone.rotate-way', null); + + keybinding.off(); + }; + + return mode; + } + + function Save$1(context) { + var ui = Commit(context) + .on('cancel', cancel) + .on('save', save); + + function cancel() { + context.enter(Browse(context)); + } + + function save(e, tryAgain) { + function withChildNodes(ids, graph) { + return _.uniq(_.reduce(ids, function(result, id) { + var e = graph.entity(id); + if (e.type === 'way') { + try { + var cn = graph.childNodes(e); + result.push.apply(result, _.map(_.filter(cn, 'version'), 'id')); + } catch (err) { + /* eslint-disable no-console */ + if (typeof console !== 'undefined') console.error(err); + /* eslint-enable no-console */ + } + } + return result; + }, _.clone(ids))); + } + + var loading = Loading(context).message(t('save.uploading')).blocking(true), + history = context.history(), + origChanges = history.changes(DiscardTags(history.difference())), + localGraph = context.graph(), + remoteGraph = Graph(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 + context.container().call(loading); + + if (toCheck.length) { + context.connection().loadMultiple(toLoad, loaded); + } else { + finalize(); + } + + + // Reload modified entities into an alternate graph and check for conflicts.. + function loaded(err, result) { + if (errors.length) return; + + if (err) { + errors.push({ + msg: err.responseText, + details: [ t('save.status_code', { code: err.status }) ] + }); + showErrors(); + + } else { + var loadMore = []; + _.each(result.data, function(entity) { + remoteGraph.replace(entity); + toLoad = _.without(toLoad, entity.id); + + // Because loadMultiple doesn't download /full like loadEntity, + // need to also load children that aren't already being checked.. + if (!entity.visible) return; + if (entity.type === 'way') { + loadMore.push.apply(loadMore, + _.difference(entity.nodes, toCheck, toLoad, loadMore)); + } else if (entity.type === 'relation' && entity.isMultipolygon()) { + loadMore.push.apply(loadMore, + _.difference(_.map(entity.members, 'id'), toCheck, toLoad, loadMore)); + } + }); + + if (loadMore.length) { + toLoad.push.apply(toLoad, loadMore); + context.connection().loadMultiple(loadMore, loaded); + } + + if (!toLoad.length) { + checkConflicts(); + } + } + } + + + function checkConflicts() { + function choice(id, text, action) { + return { id: id, text: text, action: function() { history.replace(action); } }; + } + function formatUser(d) { + return '' + d + ''; + } + function entityName(entity) { + return displayName(entity) || (displayType(entity.id) + ' ' + entity.id); + } + + function compareVersions(local, remote) { + if (local.version !== remote.version) return false; + + if (local.type === 'way') { + var children = _.union(local.nodes, remote.nodes); + + for (var i = 0; i < children.length; i++) { + var a = localGraph.hasEntity(children[i]), + b = remoteGraph.hasEntity(children[i]); + + if (a && b && a.version !== b.version) return false; + } + } + + return true; + } + + _.each(toCheck, function(id) { + var local = localGraph.entity(id), + remote = remoteGraph.entity(id); + + if (compareVersions(local, remote)) return; + + var action = MergeRemoteChanges, + merge = action(id, localGraph, remoteGraph, formatUser); + + history.replace(merge); + + var mergeConflicts = merge.conflicts(); + if (!mergeConflicts.length) return; // merged safely + + var forceLocal = action(id, localGraph, remoteGraph).withOption('force_local'), + forceRemote = action(id, localGraph, remoteGraph).withOption('force_remote'), + keepMine = t('save.conflict.' + (remote.visible ? 'keep_local' : 'restore')), + keepTheirs = t('save.conflict.' + (remote.visible ? 'keep_remote' : 'delete')); + + conflicts.push({ + id: id, + name: entityName(local), + details: mergeConflicts, + chosen: 1, + choices: [ + choice(id, keepMine, forceLocal), + choice(id, keepTheirs, forceRemote) + ] + }); + }); + + finalize(); + } + + + function finalize() { + if (conflicts.length) { + conflicts.sort(function(a,b) { return b.id.localeCompare(a.id); }); + showConflicts(); + } else if (errors.length) { + showErrors(); + } else { + var changes = history.changes(DiscardTags(history.difference())); + if (changes.modified.length || changes.created.length || changes.deleted.length) { + context.connection().putChangeset( + changes, + e.comment, + history.imageryUsed(), + function(err, changeset_id) { + if (err) { + errors.push({ + msg: err.responseText, + details: [ t('save.status_code', { code: err.status }) ] + }); + showErrors(); + } else { + history.clearSaved(); + success(e, changeset_id); + // Add delay to allow for postgres replication #1646 #2678 + window.setTimeout(function() { + loading.close(); + context.flush(); + }, 2500); + } + }); + } else { // changes were insignificant or reverted by user + loading.close(); + context.flush(); + cancel(); + } + } + } + + + function showConflicts() { + var selection = context.container() + .select('#sidebar') + .append('div') + .attr('class','sidebar-component'); + + loading.close(); + + selection.call(Conflicts(context) + .list(conflicts) + .on('download', function() { + var data = JXON.stringify(context.connection().osmChangeJXON('CHANGEME', origChanges)), + win = window.open('data:text/xml,' + encodeURIComponent(data), '_blank'); + win.focus(); + }) + .on('cancel', function() { + history.pop(); + selection.remove(); + }) + .on('save', function() { + for (var i = 0; i < conflicts.length; i++) { + if (conflicts[i].chosen === 1) { // user chose "keep theirs" + var entity = context.hasEntity(conflicts[i].id); + 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(Revert(conflicts[i].id)); + } + } + + selection.remove(); + save(e, true); + }) + ); + } + + + function showErrors() { + var selection = confirm(context.container()); + + history.pop(); + loading.close(); + + selection + .select('.modal-section.header') + .append('h3') + .text(t('save.error')); + + addErrors(selection, errors); + selection.okButton(); + } + + + function addErrors(selection, data) { + var message = selection + .select('.modal-section.message-text'); + + var items = message + .selectAll('.error-container') + .data(data); + + var enter = items.enter() + .append('div') + .attr('class', 'error-container'); + + enter + .append('a') + .attr('class', 'error-description') + .attr('href', '#') + .classed('hide-toggle', true) + .text(function(d) { return d.msg || t('save.unknown_error_details'); }) + .on('click', function() { + var error = d3.select(this), + detail = d3.select(this.nextElementSibling), + exp = error.classed('expanded'); + + detail.style('display', exp ? 'none' : 'block'); + error.classed('expanded', !exp); + + d3.event.preventDefault(); + }); + + var details = enter + .append('div') + .attr('class', 'error-detail-container') + .style('display', 'none'); + + details + .append('ul') + .attr('class', 'error-detail-list') + .selectAll('li') + .data(function(d) { return d.details || []; }) + .enter() + .append('li') + .attr('class', 'error-detail-item') + .text(function(d) { return d; }); + + items.exit() + .remove(); + } + + } + + + function success(e, changeset_id) { + context.enter(Browse(context) + .sidebar(Success(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) { + if (err) { + cancel(); + } else { + context.ui().sidebar.show(ui); + } + }); + }; + + mode.exit = function() { + context.ui().sidebar.hide(); + }; + + return mode; + } + + function Circularize$1(selectedIDs, context) { + var entityId = selectedIDs[0], + entity = context.entity(entityId), + extent = entity.extent(context.graph()), + geometry = context.geometry(entityId), + action = iD.actions.Circularize(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) { + reason = 'too_large'; + } else if (context.hasHiddenConnections(entityId)) { + reason = 'connected_to_hidden'; + } + return action.disabled(context.graph()) || reason; + }; + + operation.tooltip = function() { + var disable = operation.disabled(); + return disable ? + t('operations.circularize.' + disable) : + t('operations.circularize.description.' + geometry); + }; + + operation.id = 'circularize'; + operation.keys = [t('operations.circularize.key')]; + operation.title = t('operations.circularize.title'); + + return operation; + } + + function Continue(selectedIDs, context) { + var graph = context.graph(), + entities = selectedIDs.map(function(id) { return graph.entity(id); }), + 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' && + parent.affix(vertex.id) && + (geometries.line.length === 0 || geometries.line[0] === parent); + }); + } + + var operation = function() { + var candidate = candidateWays()[0]; + context.enter(iD.modes.DrawLine( + 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) + return 'not_eligible'; + if (candidates.length > 1) + return 'multiple'; + }; + + operation.tooltip = function() { + var disable = operation.disabled(); + return disable ? + t('operations.continue.' + disable) : + t('operations.continue.description'); + }; + + operation.id = 'continue'; + operation.keys = [t('operations.continue.key')]; + operation.title = t('operations.continue.title'); + + return operation; + } + + function Delete(selectedIDs, context) { + var action = iD.actions.DeleteMultiple(selectedIDs); + + var operation = function() { + var annotation, + nextSelectedID; + + if (selectedIDs.length > 1) { + annotation = t('operations.delete.annotation.multiple', {n: selectedIDs.length}); + + } else { + var id = selectedIDs[0], + entity = context.entity(id), + geometry = context.geometry(id), + parents = context.graph().parentWays(entity), + parent = parents[0]; + + annotation = t('operations.delete.annotation.' + geometry); + + // Select the next closest node in the way. + if (geometry === 'vertex' && parents.length === 1 && parent.nodes.length > 2) { + var nodes = parent.nodes, + i = nodes.indexOf(id); + + if (i === 0) { + i++; + } else if (i === nodes.length - 1) { + i--; + } else { + var a = iD.geo.sphericalDistance(entity.loc, context.entity(nodes[i - 1]).loc), + b = iD.geo.sphericalDistance(entity.loc, context.entity(nodes[i + 1]).loc); + i = a < b ? i - 1 : i + 1; + } + + nextSelectedID = nodes[i]; + } + } + + if (nextSelectedID && context.hasEntity(nextSelectedID)) { + context.enter(iD.modes.Select(context, [nextSelectedID])); + } else { + context.enter(iD.modes.Browse(context)); + } + + context.perform( + action, + annotation); + }; + + operation.available = function() { + return true; + }; + + operation.disabled = function() { + var reason; + if (_.some(selectedIDs, context.hasHiddenConnections)) { + reason = 'connected_to_hidden'; + } + return action.disabled(context.graph()) || reason; + }; + + operation.tooltip = function() { + var disable = operation.disabled(); + return disable ? + t('operations.delete.' + disable) : + t('operations.delete.description'); + }; + + operation.id = 'delete'; + operation.keys = [iD.ui.cmd('⌘⌫'), iD.ui.cmd('⌘⌦')]; + operation.title = t('operations.delete.title'); + + return operation; + } + + function Disconnect$1(selectedIDs, context) { + var vertices = _.filter(selectedIDs, function vertex(entityId) { + return context.geometry(entityId) === 'vertex'; + }); + + var entityId = vertices[0], + action = iD.actions.Disconnect(entityId); + + if (selectedIDs.length > 1) { + action.limitWays(_.without(selectedIDs, entityId)); + } + + var operation = function() { + context.perform(action, t('operations.disconnect.annotation')); + }; + + operation.available = function() { + return vertices.length === 1; + }; + + operation.disabled = function() { + var reason; + if (_.some(selectedIDs, context.hasHiddenConnections)) { + reason = 'connected_to_hidden'; + } + return action.disabled(context.graph()) || reason; + }; + + operation.tooltip = function() { + var disable = operation.disabled(); + return disable ? + t('operations.disconnect.' + disable) : + t('operations.disconnect.description'); + }; + + operation.id = 'disconnect'; + operation.keys = [t('operations.disconnect.key')]; + operation.title = t('operations.disconnect.title'); + + return operation; + } + + function Merge$1(selectedIDs, context) { + var join = iD.actions.Join(selectedIDs), + merge = iD.actions.Merge(selectedIDs), + mergePolygon = iD.actions.MergePolygon(selectedIDs); + + var operation = function() { + var annotation = t('operations.merge.annotation', {n: selectedIDs.length}), + action; + + if (!join.disabled(context.graph())) { + action = join; + } else if (!merge.disabled(context.graph())) { + action = merge; + } else { + action = mergePolygon; + } + + context.perform(action, annotation); + context.enter(iD.modes.Select(context, selectedIDs.filter(function(id) { return context.hasEntity(id); })) + .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 (p === 'incomplete_relation' && j && m) + return t('operations.merge.incomplete_relation'); + + 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; + } + + function Move(selectedIDs, context) { + var extent = selectedIDs.reduce(function(extent, id) { + return extent.extend(context.entity(id).extent(context.graph())); + }, iD.geo.Extent()); + + var operation = function() { + context.enter(iD.modes.Move(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) { + reason = 'too_large'; + } else if (_.some(selectedIDs, context.hasHiddenConnections)) { + reason = 'connected_to_hidden'; + } + return iD.actions.Move(selectedIDs).disabled(context.graph()) || reason; + }; + + operation.tooltip = function() { + var disable = operation.disabled(); + return disable ? + t('operations.move.' + disable) : + t('operations.move.description'); + }; + + operation.id = 'move'; + operation.keys = [t('operations.move.key')]; + operation.title = t('operations.move.title'); + + return operation; + } + + function Orthogonalize$1(selectedIDs, context) { + var entityId = selectedIDs[0], + entity = context.entity(entityId), + extent = entity.extent(context.graph()), + geometry = context.geometry(entityId), + action = iD.actions.Orthogonalize(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' && + entity.isClosed() && + _.uniq(entity.nodes).length > 2; + }; + + operation.disabled = function() { + var reason; + if (extent.percentContainedIn(context.extent()) < 0.8) { + reason = 'too_large'; + } else if (context.hasHiddenConnections(entityId)) { + reason = 'connected_to_hidden'; + } + return action.disabled(context.graph()) || reason; + }; + + operation.tooltip = function() { + var disable = operation.disabled(); + return disable ? + t('operations.orthogonalize.' + disable) : + t('operations.orthogonalize.description.' + geometry); + }; + + operation.id = 'orthogonalize'; + operation.keys = [t('operations.orthogonalize.key')]; + operation.title = t('operations.orthogonalize.title'); + + return operation; + } + + function Reverse$1(selectedIDs, context) { + var entityId = selectedIDs[0]; + + var operation = function() { + context.perform( + iD.actions.Reverse(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; + } + + function Rotate(selectedIDs, context) { + var entityId = selectedIDs[0], + entity = context.entity(entityId), + extent = entity.extent(context.graph()), + geometry = context.geometry(entityId); + + var operation = function() { + context.enter(iD.modes.RotateWay(context, entityId)); + }; + + operation.available = function() { + if (selectedIDs.length !== 1 || entity.type !== 'way') + return false; + if (geometry === 'area') + return true; + if (entity.isClosed() && + context.graph().parentRelations(entity).some(function(r) { return r.isMultipolygon(); })) + return true; + return false; + }; + + operation.disabled = function() { + if (extent.percentContainedIn(context.extent()) < 0.8) { + return 'too_large'; + } else if (context.hasHiddenConnections(entityId)) { + return 'connected_to_hidden'; + } else { + return false; + } + }; + + operation.tooltip = function() { + var disable = operation.disabled(); + return disable ? + t('operations.rotate.' + disable) : + t('operations.rotate.description'); + }; + + operation.id = 'rotate'; + operation.keys = [t('operations.rotate.key')]; + operation.title = t('operations.rotate.title'); + + return operation; + } + + function Split$1(selectedIDs, context) { + var vertices = _.filter(selectedIDs, function vertex(entityId) { + return context.geometry(entityId) === 'vertex'; + }); + + var entityId = vertices[0], + action = iD.actions.Split(entityId); + + if (selectedIDs.length > 1) { + action.limitWays(_.without(selectedIDs, entityId)); + } + + var operation = function() { + var annotation; + + var ways = action.ways(context.graph()); + if (ways.length === 1) { + annotation = t('operations.split.annotation.' + context.geometry(ways[0].id)); + } else { + annotation = t('operations.split.annotation.multiple', {n: ways.length}); + } + + var difference = context.perform(action, annotation); + context.enter(iD.modes.Select(context, difference.extantIDs())); + }; + + operation.available = function() { + return vertices.length === 1; + }; + + operation.disabled = function() { + var reason; + if (_.some(selectedIDs, context.hasHiddenConnections)) { + reason = 'connected_to_hidden'; + } + return action.disabled(context.graph()) || reason; + }; + + operation.tooltip = function() { + var disable = operation.disabled(); + if (disable) { + return t('operations.split.' + disable); + } + + var ways = action.ways(context.graph()); + if (ways.length === 1) { + return t('operations.split.description.' + context.geometry(ways[0].id)); + } else { + return t('operations.split.description.multiple'); + } + }; + + operation.id = 'split'; + operation.keys = [t('operations.split.key')]; + operation.title = t('operations.split.title'); + + return operation; + } + + function Straighten$1(selectedIDs, context) { + var entityId = selectedIDs[0], + action = iD.actions.Straighten(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 && + entity.type === 'way' && + !entity.isClosed() && + _.uniq(entity.nodes).length > 2; + }; + + operation.disabled = function() { + var reason; + if (context.hasHiddenConnections(entityId)) { + reason = 'connected_to_hidden'; + } + return action.disabled(context.graph()) || reason; + }; + + operation.tooltip = function() { + var disable = operation.disabled(); + return disable ? + t('operations.straighten.' + disable) : + t('operations.straighten.description'); + }; + + operation.id = 'straighten'; + operation.keys = [t('operations.straighten.key')]; + operation.title = t('operations.straighten.title'); + + return operation; + } + + + + var Operations = Object.freeze({ + Circularize: Circularize$1, + Continue: Continue, + Delete: Delete, + Disconnect: Disconnect$1, + Merge: Merge$1, + Move: Move, + Orthogonalize: Orthogonalize$1, + Reverse: Reverse$1, + Rotate: Rotate, + Split: Split$1, + Straighten: Straighten$1 + }); + function SelectMode(context, selectedIDs) { var mode = { id: 'select', @@ -8345,13 +10604,13 @@ var keybinding = d3.keybinding('select'), timeout = null, behaviors = [ - iD.behavior.Copy(context), - iD.behavior.Paste(context), - iD.behavior.Breathe(context), - iD.behavior.Hover(context), - iD.behavior.Select(context), - iD.behavior.Lasso(context), - iD.modes.DragNode(context) + Copy(context), + Paste(context), + Breathe(context), + Hover(context), + Select(context), + Lasso(context), + DragNode(context) .selectedIDs(selectedIDs) .behavior], inspector, @@ -8385,8 +10644,8 @@ radialMenu.center(context.projection(entity.loc)); } else { var point = context.mouse(), - viewport = iD.geo.Extent(context.projection.clipExtent()).polygon(); - if (iD.geo.pointInPolygon(point, viewport)) { + viewport = Extent(context.projection.clipExtent()).polygon(); + if (pointInPolygon(point, viewport)) { radialMenu.center(point); } else { suppressMenu = true; @@ -8440,7 +10699,7 @@ closeMenu(); if (_.some(selectedIDs, function(id) { return !context.hasEntity(id); })) { // Exit mode if selected entity gets undone - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } } @@ -8448,15 +10707,15 @@ var target = d3.select(d3.event.target), datum = target.datum(); - if (datum instanceof iD.Way && !target.classed('fill')) { - var choice = iD.geo.chooseEdge(context.childNodes(datum), context.mouse(), context.projection), - node = iD.Node(); + if (datum instanceof Way && !target.classed('fill')) { + var choice = chooseEdge(context.childNodes(datum), context.mouse(), context.projection), + node = Node(); var prev = datum.nodes[choice.index - 1], next = datum.nodes[choice.index]; context.perform( - iD.actions.AddMidpoint({loc: choice.loc, edge: [prev, next]}, node), + AddMidpoint({loc: choice.loc, edge: [prev, next]}, node), t('operations.add.annotation.vertex')); d3.event.preventDefault(); @@ -8472,11 +10731,11 @@ } var selection = context.surface() - .selectAll(iD.util.entityOrMemberSelector(selectedIDs, context.graph())); + .selectAll(entityOrMemberSelector(selectedIDs, context.graph())); if (selection.empty()) { if (drawn) { // Exit mode if selected DOM elements have disappeared.. - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } } else { selection @@ -8486,7 +10745,7 @@ function esc() { if (!context.inIntro()) { - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } } @@ -8495,11 +10754,11 @@ context.install(behavior); }); - var operations = _.without(d3.values(iD.operations), iD.operations.Delete) + var operations = _.without(d3.values(Operations), Delete) .map(function(o) { return o(selectedIDs, context); }) .filter(function(o) { return o.available(); }); - operations.unshift(iD.operations.Delete(selectedIDs, context)); + operations.unshift(Delete(selectedIDs, context)); keybinding .on('⎋', esc, true) @@ -8518,7 +10777,7 @@ d3.select(document) .call(keybinding); - radialMenu = iD.ui.RadialMenu(context, operations); + radialMenu = RadialMenu(context, operations); context.ui().sidebar .select(singular() ? singular().id : null, newFeature); @@ -8549,7 +10808,7 @@ }, 200); if (selectedIDs.length > 1) { - var entities = iD.ui.SelectionList(context, selectedIDs); + var entities = SelectionList(context, selectedIDs); context.ui().sidebar.show(entities); } }; @@ -8583,6 +10842,22 @@ return mode; } + + + var modes = Object.freeze({ + AddArea: AddArea, + AddLine: AddLine, + AddPoint: AddPoint, + Browse: Browse, + DragNode: DragNode, + DrawArea: DrawArea, + DrawLine: DrawLine, + Move: MoveMode, + RotateWay: RotateWay, + Save: Save$1, + Select: SelectMode + }); + function Edit(context) { function edit() { context.map() @@ -9976,7 +12251,11 @@ exports.geo = geo; exports.svg = svg; exports.behavior = behavior; +<<<<<<< HEAD >>>>>>> ef619c2... external modules for behavior +======= + exports.modes = modes; +>>>>>>> 75901f6... external modules for modes exports.Connection = Connection; exports.Difference = Difference; exports.Entity = Entity; diff --git a/modules/index.js b/modules/index.js index 2747b6f0a..4ab67166b 100644 --- a/modules/index.js +++ b/modules/index.js @@ -1,6 +1,7 @@ import * as actions from './actions/index'; import * as geo from './geo/index'; import * as behavior from './behavior/index'; +import * as modes from './modes/index'; export { Connection } from './core/connection'; export { Difference } from './core/difference'; @@ -16,5 +17,6 @@ export { Way } from './core/way'; export { actions, geo, - behavior + behavior, + modes }; diff --git a/modules/modes/add_area.js b/modules/modes/add_area.js index 77fdf1151..73a36a23c 100644 --- a/modules/modes/add_area.js +++ b/modules/modes/add_area.js @@ -1,3 +1,7 @@ +import { AddWay } from '../behavior/index'; +import { Node, Way } from '../core/index'; +import { DrawArea } from './index'; +import { AddEntity, AddVertex, AddMidpoint } from '../actions/index'; export function AddArea(context) { var mode = { id: 'add-area', @@ -7,7 +11,7 @@ export function AddArea(context) { key: '3' }; - var behavior = iD.behavior.AddWay(context) + var behavior = AddWay(context) .tail(t('modes.add_area.tail')) .on('start', start) .on('startFromWay', startFromWay) @@ -16,43 +20,43 @@ export function AddArea(context) { function start(loc) { var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); + node = Node({loc: loc}), + way = Way({tags: defaultTags}); context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); + AddEntity(node), + AddEntity(way), + AddVertex(way.id, node.id), + AddVertex(way.id, node.id)); - context.enter(iD.modes.DrawArea(context, way.id, graph)); + context.enter(DrawArea(context, way.id, graph)); } function startFromWay(loc, edge) { var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); + node = Node({loc: loc}), + way = Way({tags: defaultTags}); context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddMidpoint({ loc: loc, edge: edge }, node)); + AddEntity(node), + AddEntity(way), + AddVertex(way.id, node.id), + AddVertex(way.id, node.id), + AddMidpoint({ loc: loc, edge: edge }, node)); - context.enter(iD.modes.DrawArea(context, way.id, graph)); + context.enter(DrawArea(context, way.id, graph)); } function startFromNode(node) { var graph = context.graph(), - way = iD.Way({tags: defaultTags}); + way = Way({tags: defaultTags}); context.perform( - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); + AddEntity(way), + AddVertex(way.id, node.id), + AddVertex(way.id, node.id)); - context.enter(iD.modes.DrawArea(context, way.id, graph)); + context.enter(DrawArea(context, way.id, graph)); } mode.enter = function() { diff --git a/modules/modes/add_line.js b/modules/modes/add_line.js index ba8f37ef6..d053571d2 100644 --- a/modules/modes/add_line.js +++ b/modules/modes/add_line.js @@ -1,3 +1,7 @@ +import { AddWay } from '../behavior/index'; +import { Node, Way } from '../core/index'; +import { DrawLine } from './index'; +import { AddEntity, AddVertex, AddMidpoint } from '../actions/index'; export function AddLine(context) { var mode = { id: 'add-line', @@ -7,7 +11,7 @@ export function AddLine(context) { key: '2' }; - var behavior = iD.behavior.AddWay(context) + var behavior = AddWay(context) .tail(t('modes.add_line.tail')) .on('start', start) .on('startFromWay', startFromWay) @@ -15,40 +19,40 @@ export function AddLine(context) { function start(loc) { var baseGraph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way(); + node = Node({loc: loc}), + way = Way(); context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); + AddEntity(node), + AddEntity(way), + AddVertex(way.id, node.id)); - context.enter(iD.modes.DrawLine(context, way.id, baseGraph)); + context.enter(DrawLine(context, way.id, baseGraph)); } function startFromWay(loc, edge) { var baseGraph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way(); + node = Node({loc: loc}), + way = Way(); context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddMidpoint({ loc: loc, edge: edge }, node)); + AddEntity(node), + AddEntity(way), + AddVertex(way.id, node.id), + AddMidpoint({ loc: loc, edge: edge }, node)); - context.enter(iD.modes.DrawLine(context, way.id, baseGraph)); + context.enter(DrawLine(context, way.id, baseGraph)); } function startFromNode(node) { var baseGraph = context.graph(), - way = iD.Way(); + way = Way(); context.perform( - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); + AddEntity(way), + AddVertex(way.id, node.id)); - context.enter(iD.modes.DrawLine(context, way.id, baseGraph)); + context.enter(DrawLine(context, way.id, baseGraph)); } mode.enter = function() { diff --git a/modules/modes/add_point.js b/modules/modes/add_point.js index 2e33a1ee1..77d6f295b 100644 --- a/modules/modes/add_point.js +++ b/modules/modes/add_point.js @@ -1,3 +1,7 @@ +import { Draw } from '../behavior/index'; +import { Node } from '../core/index'; +import { Select, Browse } from './index'; +import { AddEntity } from '../actions/index'; export function AddPoint(context) { var mode = { id: 'add-point', @@ -7,7 +11,7 @@ export function AddPoint(context) { key: '1' }; - var behavior = iD.behavior.Draw(context) + var behavior = Draw(context) .tail(t('modes.add_point.tail')) .on('click', add) .on('clickWay', addWay) @@ -16,14 +20,14 @@ export function AddPoint(context) { .on('finish', cancel); function add(loc) { - var node = iD.Node({loc: loc}); + var node = Node({loc: loc}); context.perform( - iD.actions.AddEntity(node), + AddEntity(node), t('operations.add.annotation.point')); context.enter( - iD.modes.Select(context, [node.id]) + Select(context, [node.id]) .suppressMenu(true) .newFeature(true)); } @@ -37,7 +41,7 @@ export function AddPoint(context) { } function cancel() { - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } mode.enter = function() { diff --git a/modules/modes/browse.js b/modules/modes/browse.js index 345604629..caa501a2d 100644 --- a/modules/modes/browse.js +++ b/modules/modes/browse.js @@ -1,3 +1,5 @@ +import { Paste, Hover, Select, Lasso } from '../behavior/index'; +import { DragNode } from './index'; export function Browse(context) { var mode = { button: 'browse', @@ -7,12 +9,12 @@ export function Browse(context) { }, sidebar; var behaviors = [ - iD.behavior.Paste(context), - iD.behavior.Hover(context) + Paste(context), + Hover(context) .on('hover', context.ui().sidebar.hover), - iD.behavior.Select(context), - iD.behavior.Lasso(context), - iD.modes.DragNode(context).behavior]; + Select(context), + Lasso(context), + DragNode(context).behavior]; mode.enter = function() { behaviors.forEach(function(behavior) { diff --git a/modules/modes/drag_node.js b/modules/modes/drag_node.js index 255f64e2a..3cabc7455 100644 --- a/modules/modes/drag_node.js +++ b/modules/modes/drag_node.js @@ -1,3 +1,9 @@ +import { Hover, Edit, drag } from '../behavior/index'; +import { Node } from '../core/index'; +import { entitySelector } from '../util/index'; +import { Select, Browse } from './index'; +import { chooseEdge } from '../geo/index'; +import { AddMidpoint, Noop, MoveNode, Connect } from '../actions/index'; export function DragNode(context) { var mode = { id: 'drag-node', @@ -9,10 +15,10 @@ export function DragNode(context) { wasMidpoint, cancelled, selectedIDs = [], - hover = iD.behavior.Hover(context) + hover = Hover(context) .altDisables(true) .on('hover', context.ui().sidebar.hover), - edit = iD.behavior.Edit(context); + edit = Edit(context); function edge(point, size) { var pad = [30, 100, 30, 100]; @@ -56,8 +62,8 @@ export function DragNode(context) { wasMidpoint = entity.type === 'midpoint'; if (wasMidpoint) { var midpoint = entity; - entity = iD.Node(); - context.perform(iD.actions.AddMidpoint(midpoint, entity)); + entity = Node(); + context.perform(AddMidpoint(midpoint, entity)); var vertex = context.surface() .selectAll('.' + entity.id); @@ -65,7 +71,7 @@ export function DragNode(context) { } else { context.perform( - iD.actions.Noop()); + Noop()); } activeIDs = _.map(context.graph().parentWays(entity), 'id'); @@ -106,11 +112,11 @@ 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 = iD.geo.chooseEdge(context.childNodes(d), context.mouse(), context.projection).loc; + loc = chooseEdge(context.childNodes(d), context.mouse(), context.projection).loc; } context.replace( - iD.actions.MoveNode(entity.id, loc), + MoveNode(entity.id, loc), moveAnnotation(entity)); } @@ -120,24 +126,24 @@ export function DragNode(context) { var d = datum(); if (d.type === 'way') { - var choice = iD.geo.chooseEdge(context.childNodes(d), context.mouse(), context.projection); + var choice = chooseEdge(context.childNodes(d), context.mouse(), context.projection); context.replace( - iD.actions.AddMidpoint({ loc: choice.loc, edge: [d.nodes[choice.index - 1], d.nodes[choice.index]] }, entity), + AddMidpoint({ 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( - iD.actions.Connect([d.id, entity.id]), + Connect([d.id, entity.id]), connectAnnotation(d)); } else if (wasMidpoint) { context.replace( - iD.actions.Noop(), + Noop(), t('operations.add.annotation.vertex')); } else { context.replace( - iD.actions.Noop(), + Noop(), moveAnnotation(entity)); } @@ -147,24 +153,24 @@ export function DragNode(context) { if (reselection.length) { context.enter( - iD.modes.Select(context, reselection) + Select(context, reselection) .suppressMenu(true)); } else { - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } } function cancel() { behavior.cancel(); - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } function setActiveElements() { - context.surface().selectAll(iD.util.entitySelector(activeIDs)) + context.surface().selectAll(entitySelector(activeIDs)) .classed('active', true); } - var behavior = iD.behavior.drag() + var behavior = drag() .delegate('g.node, g.point, g.midpoint') .surface(context.surface().node()) .origin(origin) diff --git a/modules/modes/draw_area.js b/modules/modes/draw_area.js index 2c923d43a..b98b98caf 100644 --- a/modules/modes/draw_area.js +++ b/modules/modes/draw_area.js @@ -1,3 +1,4 @@ +import { DrawWay } from '../behavior/index'; export function DrawArea(context, wayId, baseGraph) { var mode = { button: 'area', @@ -11,7 +12,7 @@ export function DrawArea(context, wayId, baseGraph) { headId = way.nodes[way.nodes.length - 2], tailId = way.first(); - behavior = iD.behavior.DrawWay(context, wayId, -1, mode, baseGraph) + behavior = DrawWay(context, wayId, -1, mode, baseGraph) .tail(t('modes.draw_area.tail')); var addNode = behavior.addNode; diff --git a/modules/modes/draw_line.js b/modules/modes/draw_line.js index dc71e59d2..1c0d8a4f5 100644 --- a/modules/modes/draw_line.js +++ b/modules/modes/draw_line.js @@ -1,3 +1,4 @@ +import { DrawWay } from '../behavior/index'; export function DrawLine(context, wayId, baseGraph, affix) { var mode = { button: 'line', @@ -11,7 +12,7 @@ export function DrawLine(context, wayId, baseGraph, affix) { index = (affix === 'prefix') ? 0 : undefined, headId = (affix === 'prefix') ? way.first() : way.last(); - behavior = iD.behavior.DrawWay(context, wayId, index, mode, baseGraph) + behavior = DrawWay(context, wayId, index, mode, baseGraph) .tail(t('modes.draw_line.tail')); var addNode = behavior.addNode; diff --git a/modules/modes/move.js b/modules/modes/move.js index 8075d2ec9..8cf00236a 100644 --- a/modules/modes/move.js +++ b/modules/modes/move.js @@ -1,3 +1,6 @@ +import { Edit } from '../behavior/index'; +import { Select, Browse } from './index'; +import { Move as MoveAction, Noop } from '../actions/index'; export function Move(context, entityIDs, baseGraph) { var mode = { id: 'move', @@ -5,7 +8,7 @@ export function Move(context, entityIDs, baseGraph) { }; var keybinding = d3.keybinding('move'), - edit = iD.behavior.Edit(context), + edit = Edit(context), annotation = entityIDs.length === 1 ? t('operations.move.annotation.' + context.geometry(entityIDs[0])) : t('operations.move.annotation.multiple'), @@ -32,7 +35,7 @@ export function Move(context, entityIDs, baseGraph) { var currMouse = context.mouse(), origMouse = context.projection(origin), delta = vecSub(vecSub(currMouse, origMouse), nudge), - action = iD.actions.Move(entityIDs, delta, context.projection, cache); + action = MoveAction(entityIDs, delta, context.projection, cache); context.overwrite(action, annotation); @@ -48,7 +51,7 @@ export function Move(context, entityIDs, baseGraph) { var currMouse = context.mouse(), origMouse = context.projection(origin), delta = vecSub(currMouse, origMouse), - action = iD.actions.Move(entityIDs, delta, context.projection, cache); + action = MoveAction(entityIDs, delta, context.projection, cache); context.overwrite(action, annotation); @@ -59,23 +62,23 @@ export function Move(context, entityIDs, baseGraph) { function finish() { d3.event.stopPropagation(); - context.enter(iD.modes.Select(context, entityIDs).suppressMenu(true)); + context.enter(Select(context, entityIDs).suppressMenu(true)); stopNudge(); } function cancel() { if (baseGraph) { while (context.graph() !== baseGraph) context.pop(); - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } else { context.pop(); - context.enter(iD.modes.Select(context, entityIDs).suppressMenu(true)); + context.enter(Select(context, entityIDs).suppressMenu(true)); } stopNudge(); } function undone() { - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } mode.enter = function() { @@ -85,7 +88,7 @@ export function Move(context, entityIDs, baseGraph) { context.install(edit); context.perform( - iD.actions.Noop(), + Noop(), annotation); context.surface() diff --git a/modules/modes/rotate_way.js b/modules/modes/rotate_way.js index 54072394c..d59dc9549 100644 --- a/modules/modes/rotate_way.js +++ b/modules/modes/rotate_way.js @@ -1,3 +1,6 @@ +import { Edit } from '../behavior/index'; +import { Select, Browse } from './index'; +import { Noop, RotateWay as RotateWayAction } from '../actions/index'; export function RotateWay(context, wayId) { var mode = { id: 'rotate-way', @@ -5,7 +8,7 @@ export function RotateWay(context, wayId) { }; var keybinding = d3.keybinding('rotate-way'), - edit = iD.behavior.Edit(context); + edit = Edit(context); mode.enter = function() { context.install(edit); @@ -18,7 +21,7 @@ export function RotateWay(context, wayId) { angle; context.perform( - iD.actions.Noop(), + Noop(), annotation); function rotate() { @@ -29,7 +32,7 @@ export function RotateWay(context, wayId) { if (typeof angle === 'undefined') angle = newAngle; context.replace( - iD.actions.RotateWay(wayId, pivot, newAngle - angle, context.projection), + RotateWayAction(wayId, pivot, newAngle - angle, context.projection), annotation); angle = newAngle; @@ -37,18 +40,18 @@ export function RotateWay(context, wayId) { function finish() { d3.event.stopPropagation(); - context.enter(iD.modes.Select(context, [wayId]) + context.enter(Select(context, [wayId]) .suppressMenu(true)); } function cancel() { context.pop(); - context.enter(iD.modes.Select(context, [wayId]) + context.enter(Select(context, [wayId]) .suppressMenu(true)); } function undone() { - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } context.surface() diff --git a/modules/modes/save.js b/modules/modes/save.js index bc6acdeb4..b0dd943c7 100644 --- a/modules/modes/save.js +++ b/modules/modes/save.js @@ -1,10 +1,16 @@ +import { Graph } from '../core/index'; +import { displayName, displayType } from '../util/index'; +import { Browse } from './index'; +import { DiscardTags, Noop, MergeRemoteChanges, Revert } from '../actions/index'; +import { Commit, Loading, Success, Conflicts } from '../ui/core/index'; + export function Save(context) { - var ui = iD.ui.Commit(context) + var ui = Commit(context) .on('cancel', cancel) .on('save', save); function cancel() { - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } function save(e, tryAgain) { @@ -25,18 +31,18 @@ export function Save(context) { }, _.clone(ids))); } - var loading = iD.ui.Loading(context).message(t('save.uploading')).blocking(true), + var loading = Loading(context).message(t('save.uploading')).blocking(true), history = context.history(), - origChanges = history.changes(iD.actions.DiscardTags(history.difference())), + origChanges = history.changes(DiscardTags(history.difference())), localGraph = context.graph(), - remoteGraph = iD.Graph(history.base(), true), + remoteGraph = Graph(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(iD.actions.Noop()); // checkpoint + if (!tryAgain) history.perform(Noop()); // checkpoint context.container().call(loading); if (toCheck.length) { @@ -95,7 +101,7 @@ export function Save(context) { return '' + d + ''; } function entityName(entity) { - return iD.util.displayName(entity) || (iD.util.displayType(entity.id) + ' ' + entity.id); + return displayName(entity) || (displayType(entity.id) + ' ' + entity.id); } function compareVersions(local, remote) { @@ -121,7 +127,7 @@ export function Save(context) { if (compareVersions(local, remote)) return; - var action = iD.actions.MergeRemoteChanges, + var action = MergeRemoteChanges, merge = action(id, localGraph, remoteGraph, formatUser); history.replace(merge); @@ -157,7 +163,7 @@ export function Save(context) { } else if (errors.length) { showErrors(); } else { - var changes = history.changes(iD.actions.DiscardTags(history.difference())); + var changes = history.changes(DiscardTags(history.difference())); if (changes.modified.length || changes.created.length || changes.deleted.length) { context.connection().putChangeset( changes, @@ -197,7 +203,7 @@ export function Save(context) { loading.close(); - selection.call(iD.ui.Conflicts(context) + selection.call(Conflicts(context) .list(conflicts) .on('download', function() { var data = JXON.stringify(context.connection().osmChangeJXON('CHANGEME', origChanges)), @@ -215,10 +221,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(iD.actions.Revert(children[j])); + history.replace(Revert(children[j])); } } - history.replace(iD.actions.Revert(conflicts[i].id)); + history.replace(Revert(conflicts[i].id)); } } @@ -230,7 +236,7 @@ export function Save(context) { function showErrors() { - var selection = iD.ui.confirm(context.container()); + var selection = confirm(context.container()); history.pop(); loading.close(); @@ -297,8 +303,8 @@ export function Save(context) { function success(e, changeset_id) { - context.enter(iD.modes.Browse(context) - .sidebar(iD.ui.Success(context) + context.enter(Browse(context) + .sidebar(Success(context) .changeset({ id: changeset_id, comment: e.comment diff --git a/modules/modes/select.js b/modules/modes/select.js index 79a4ccdc7..444974526 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -1,3 +1,12 @@ +import { Copy, Paste, Breathe, Hover, Select as SelectBehavior, Lasso } from '../behavior/index'; +import { Way, Node } from '../core/index'; +import { entityOrMemberSelector } from '../util/index'; +import { DragNode, Browse } from './index'; +import { Extent, pointInPolygon, chooseEdge } from '../geo/index'; +import { AddMidpoint } from '../actions/index'; +import * as Operations from '../operations/index'; +import { RadialMenu, SelectionList } from '../ui/core/index'; + export function Select(context, selectedIDs) { var mode = { id: 'select', @@ -7,13 +16,13 @@ export function Select(context, selectedIDs) { var keybinding = d3.keybinding('select'), timeout = null, behaviors = [ - iD.behavior.Copy(context), - iD.behavior.Paste(context), - iD.behavior.Breathe(context), - iD.behavior.Hover(context), - iD.behavior.Select(context), - iD.behavior.Lasso(context), - iD.modes.DragNode(context) + Copy(context), + Paste(context), + Breathe(context), + Hover(context), + SelectBehavior(context), + Lasso(context), + DragNode(context) .selectedIDs(selectedIDs) .behavior], inspector, @@ -47,8 +56,8 @@ export function Select(context, selectedIDs) { radialMenu.center(context.projection(entity.loc)); } else { var point = context.mouse(), - viewport = iD.geo.Extent(context.projection.clipExtent()).polygon(); - if (iD.geo.pointInPolygon(point, viewport)) { + viewport = Extent(context.projection.clipExtent()).polygon(); + if (pointInPolygon(point, viewport)) { radialMenu.center(point); } else { suppressMenu = true; @@ -102,7 +111,7 @@ export function Select(context, selectedIDs) { closeMenu(); if (_.some(selectedIDs, function(id) { return !context.hasEntity(id); })) { // Exit mode if selected entity gets undone - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } } @@ -110,15 +119,15 @@ export function Select(context, selectedIDs) { var target = d3.select(d3.event.target), datum = target.datum(); - if (datum instanceof iD.Way && !target.classed('fill')) { - var choice = iD.geo.chooseEdge(context.childNodes(datum), context.mouse(), context.projection), - node = iD.Node(); + if (datum instanceof Way && !target.classed('fill')) { + var choice = chooseEdge(context.childNodes(datum), context.mouse(), context.projection), + node = Node(); var prev = datum.nodes[choice.index - 1], next = datum.nodes[choice.index]; context.perform( - iD.actions.AddMidpoint({loc: choice.loc, edge: [prev, next]}, node), + AddMidpoint({loc: choice.loc, edge: [prev, next]}, node), t('operations.add.annotation.vertex')); d3.event.preventDefault(); @@ -134,11 +143,11 @@ export function Select(context, selectedIDs) { } var selection = context.surface() - .selectAll(iD.util.entityOrMemberSelector(selectedIDs, context.graph())); + .selectAll(entityOrMemberSelector(selectedIDs, context.graph())); if (selection.empty()) { if (drawn) { // Exit mode if selected DOM elements have disappeared.. - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } } else { selection @@ -148,7 +157,7 @@ export function Select(context, selectedIDs) { function esc() { if (!context.inIntro()) { - context.enter(iD.modes.Browse(context)); + context.enter(Browse(context)); } } @@ -157,11 +166,11 @@ export function Select(context, selectedIDs) { context.install(behavior); }); - var operations = _.without(d3.values(iD.operations), iD.operations.Delete) + var operations = _.without(d3.values(Operations), Operations.Delete) .map(function(o) { return o(selectedIDs, context); }) .filter(function(o) { return o.available(); }); - operations.unshift(iD.operations.Delete(selectedIDs, context)); + operations.unshift(Operations.Delete(selectedIDs, context)); keybinding .on('⎋', esc, true) @@ -180,7 +189,7 @@ export function Select(context, selectedIDs) { d3.select(document) .call(keybinding); - radialMenu = iD.ui.RadialMenu(context, operations); + radialMenu = RadialMenu(context, operations); context.ui().sidebar .select(singular() ? singular().id : null, newFeature); @@ -211,7 +220,7 @@ export function Select(context, selectedIDs) { }, 200); if (selectedIDs.length > 1) { - var entities = iD.ui.SelectionList(context, selectedIDs); + var entities = SelectionList(context, selectedIDs); context.ui().sidebar.show(entities); } }; diff --git a/test/index.html b/test/index.html index 843d3eb56..59d9bd63c 100644 --- a/test/index.html +++ b/test/index.html @@ -42,7 +42,6 @@ -