diff --git a/data/core.yaml b/data/core.yaml index b8803aac4..ed6181215 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -35,6 +35,8 @@ en: area: Continued an area. cancel_draw: annotation: Canceled drawing. + change_role: + annotation: Changed the role of a relation member. change_tags: annotation: Changed tags. circularize: @@ -66,6 +68,13 @@ en: relation: Deleted a relation. multiple: "Deleted {n} objects." incomplete_relation: This feature can't be deleted because it hasn't been fully downloaded. + delete_member: + annotation: + point: Removed a point from a relation. + vertex: Removed a node from a relation. + line: Removed a line from a relation. + area: Removed an area from a relation. + relation: Removed a relation from a relation. connect: annotation: point: Connected a way to a point. @@ -168,12 +177,16 @@ en: view_on_osm: View on openstreetmap.org editing_feature: "Editing {feature}" all_tags: All tags + all_members: All members + all_relations: All relations + role: Role choose: Select feature type results: "{n} results for {search}" reference: View on OpenStreetMap Wiki back_tooltip: Change feature type remove: Remove search: Search + incomplete: background: title: Background description: Background settings diff --git a/dist/locales/en.json b/dist/locales/en.json index b57eaf28b..a64bd2efe 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -48,6 +48,9 @@ "cancel_draw": { "annotation": "Canceled drawing." }, + "change_role": { + "annotation": "Changed the role of a relation member." + }, "change_tags": { "annotation": "Changed tags." }, @@ -87,6 +90,15 @@ }, "incomplete_relation": "This feature can't be deleted because it hasn't been fully downloaded." }, + "delete_member": { + "annotation": { + "point": "Removed a point from a relation.", + "vertex": "Removed a node from a relation.", + "line": "Removed a line from a relation.", + "area": "Removed an area from a relation.", + "relation": "Removed a relation from a relation." + } + }, "connect": { "annotation": { "point": "Connected a way to a point.", @@ -208,12 +220,16 @@ "view_on_osm": "View on openstreetmap.org", "editing_feature": "Editing {feature}", "all_tags": "All tags", + "all_members": "All members", + "all_relations": "All relations", + "role": "Role", "choose": "Select feature type", "results": "{n} results for {search}", "reference": "View on OpenStreetMap Wiki", "back_tooltip": "Change feature type", "remove": "Remove", - "search": "Search" + "search": "Search", + "incomplete": "" }, "background": { "title": "Background", diff --git a/index.html b/index.html index 5dc6ad04d..c7a756a8d 100644 --- a/index.html +++ b/index.html @@ -92,6 +92,8 @@ + + diff --git a/js/id/ui/entity_editor.js b/js/id/ui/entity_editor.js index 8ad7939a2..e50495921 100644 --- a/js/id/ui/entity_editor.js +++ b/js/id/ui/entity_editor.js @@ -6,7 +6,9 @@ iD.ui.EntityEditor = function(context, entity) { preset, selection_, presetUI, - rawTagEditor; + rawTagEditor, + rawMemberEditor, + rawMembershipEditor; function update() { var entity = context.hasEntity(id); @@ -23,6 +25,8 @@ iD.ui.EntityEditor = function(context, entity) { presetUI.change(tags); rawTagEditor.tags(tags); + if (rawMemberEditor) rawMemberEditor.change(); + rawMembershipEditor.change(); } function entityEditor(selection, newpreset) { @@ -77,17 +81,31 @@ iD.ui.EntityEditor = function(context, entity) { .on('change', changeTags) .on('close', event.close); - rawTagEditor = iD.ui.RawTagEditor(context, entity) - .on('change', changeTags); - var tageditorpreset = editorwrap.append('div') .attr('class', 'inspector-preset cf fillL col12') .call(presetUI); + rawTagEditor = iD.ui.RawTagEditor(context, entity) + .on('change', changeTags); + editorwrap.append('div') .attr('class', 'inspector-inner raw-tag-editor col12') .call(rawTagEditor, preset.id === 'other'); + if (entity.type === 'relation') { + rawMemberEditor = iD.ui.RawMemberEditor(context, entity); + + editorwrap.append('div') + .attr('class', 'inspector-inner raw-membership-editor col12') + .call(rawMemberEditor); + } + + rawMembershipEditor = iD.ui.RawMembershipEditor(context, entity); + + editorwrap.append('div') + .attr('class', 'inspector-inner raw-membership-editor col12') + .call(rawMembershipEditor); + if (!entity.isNew()) { var osmLink = tageditorpreset.append('div') .attr('class', 'col12 inspector-inner') diff --git a/js/id/ui/raw_member_editor.js b/js/id/ui/raw_member_editor.js new file mode 100644 index 000000000..9e63c2a92 --- /dev/null +++ b/js/id/ui/raw_member_editor.js @@ -0,0 +1,117 @@ +iD.ui.RawMemberEditor = function(context, entity) { + var list, disclosure; + + var rawMemberEditor = function(selection) { + function toggled(expanded) { + if (expanded) { + selection.node().parentNode.scrollTop += 200; + } + } + + disclosure = iD.ui.Disclosure() + .title(t('inspector.all_members')) + .expanded(true) + .on('toggled', toggled) + .content(content); + + selection.call(disclosure); + }; + + rawMemberEditor.change = function() { + drawMembers(); + }; + + function content(wrap) { + list = wrap.append('ul') + .attr('class', 'member-list'); + + drawMembers(); + } + + function selectMember(d) { + context.enter(iD.modes.Select(context, [d.member.id])); + } + + function changeRole(d) { + var role = d3.select(this).property('value'); + context.perform( + iD.actions.ChangeMember(entity.id, _.extend({}, d.member, {role: role}), d.index), + t('operations.change_role.annotation')); + } + + function deleteMember(d) { + context.perform( + iD.actions.DeleteMember(entity.id, d.index), + t('operations.delete_member.annotation.' + context.geometry(d.member.id))); + } + + function drawMembers() { + var memberships = []; + + entity = context.hasEntity(entity.id); + if (!entity) return; + + entity.members.forEach(function(member, index) { + memberships.push({member: member, index: index, entity: context.hasEntity(member.id)}); + }); + + disclosure.title(t('inspector.all_members') + ' (' + memberships.length + ')'); + + var li = list.selectAll('li') + .data(memberships, function(d) { return iD.Entity.key(entity) + ',' + d.index; }); + + var row = li.enter().append('li') + .attr('class', 'member-row'); + + row.each(function(d) { + if (d.entity) { + var member = d3.select(this).append('a') + .attr('href', '#') + .attr('class', 'member-entity') + .on('click', selectMember); + + member.append('span') + .attr('class', 'member-entity-icon') + .each(function(d) { + return d3.select(this) + .call(iD.ui.PresetIcon(context.geometry(d.entity.id))); + }); + + member.append('span') + .attr('class', 'member-entity-name') + .text(function(d) { return iD.util.localeName(d.entity); }); + + member.append('span') + .attr('class', 'member-entity-type') + .text(function(d) { return context.presets().match(d.entity, context.graph()).name(); }); + + } else { + d3.select(this).append('span') + .attr('class', 'member-incomplete') + .text(t('inspector.incomplete')); + } + }); + + row.append('span') + .attr('class', 'member-role') + .append('input') + .property('type', 'text') + .attr('class', 'member-role-input') + .attr('maxlength', 255) + .attr('placeholder', t('inspector.role')) + .property('value', function(d) { return d.member.role; }) + .on('change', changeRole); + + row.append('button') + .attr('tabindex', -1) + .attr('class', 'member-delete') + .on('click', deleteMember) + .append('span') + .attr('class', 'icon delete'); + + li.exit() + .remove(); + } + + return rawMemberEditor; +}; diff --git a/js/id/ui/raw_membership_editor.js b/js/id/ui/raw_membership_editor.js new file mode 100644 index 000000000..8d7c2dd2c --- /dev/null +++ b/js/id/ui/raw_membership_editor.js @@ -0,0 +1,109 @@ +iD.ui.RawMembershipEditor = function(context, entity) { + var list, disclosure; + + var rawMembershipEditor = function(selection) { + function toggled(expanded) { + if (expanded) { + selection.node().parentNode.scrollTop += 200; + } + } + + disclosure = iD.ui.Disclosure() + .title(t('inspector.all_relations')) + .expanded(true) + .on('toggled', toggled) + .content(content); + + selection.call(disclosure); + }; + + rawMembershipEditor.change = function() { + drawMemberships(); + }; + + function content(wrap) { + list = wrap.append('ul') + .attr('class', 'member-list'); + + drawMemberships(); + } + + function selectRelation(d) { + context.enter(iD.modes.Select(context, [d.relation.id])); + } + + function changeRole(d) { + var role = d3.select(this).property('value'); + context.perform( + iD.actions.ChangeMember(d.relation.id, _.extend({}, d.member, {role: role}), d.index), + t('operations.change_role.annotation')); + } + + function deleteMembership(d) { + context.perform( + iD.actions.DeleteMember(d.relation.id, d.index), + t('operations.delete_member.annotation.' + context.geometry(d.member.id))); + } + + function drawMemberships() { + var memberships = []; + + context.graph().parentRelations(entity).forEach(function(relation) { + relation.members.forEach(function(member, index) { + if (member.id === entity.id) { + memberships.push({relation: relation, member: member, index: index}); + } + }) + }); + + disclosure.title(t('inspector.all_relations') + ' (' + memberships.length + ')'); + + var li = list.selectAll('li') + .data(memberships, function(d) { return iD.Entity.key(d.relation) + ',' + d.index; }); + + var row = li.enter().append('li') + .attr('class', 'member-row'); + + var relation = row.append('a') + .attr('href', '#') + .attr('class', 'member-entity') + .on('click', selectRelation); + + relation.append('span') + .attr('class', 'member-entity-icon') + .each(function(d) { + return d3.select(this) + .call(iD.ui.PresetIcon(context.geometry(d.relation.id))); + }); + + relation.append('span') + .attr('class', 'member-entity-name') + .text(function(d) { return iD.util.localeName(d.relation); }); + + relation.append('span') + .attr('class', 'member-entity-type') + .text(function(d) { return context.presets().match(d.relation, context.graph()).name(); }); + + row.append('span') + .attr('class', 'member-role') + .append('input') + .property('type', 'text') + .attr('class', 'member-role-input') + .attr('maxlength', 255) + .attr('placeholder', t('inspector.role')) + .property('value', function(d) { return d.member.role; }) + .on('change', changeRole); + + row.append('button') + .attr('tabindex', -1) + .attr('class', 'member-delete') + .on('click', deleteMembership) + .append('span') + .attr('class', 'icon delete'); + + li.exit() + .remove(); + } + + return rawMembershipEditor; +}; diff --git a/test/index.html b/test/index.html index 930bf8e5e..bb78adee4 100644 --- a/test/index.html +++ b/test/index.html @@ -89,6 +89,7 @@ +