From 49bef3d277d7ede6efc0b1b55ce15dd3085dff8a Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 14 May 2019 15:31:07 -0400 Subject: [PATCH] Fix issue where entries in the raw member editor wouldn't update after downloading the member manually (close #6358) --- modules/ui/raw_member_editor.js | 535 ++++++++++++++++---------------- 1 file changed, 269 insertions(+), 266 deletions(-) diff --git a/modules/ui/raw_member_editor.js b/modules/ui/raw_member_editor.js index 2fefcfcc5..94fd0f041 100644 --- a/modules/ui/raw_member_editor.js +++ b/modules/ui/raw_member_editor.js @@ -21,13 +21,16 @@ import { utilDisplayName, utilDisplayType, utilHighlightEntities, utilNoAuto } f export function uiRawMemberEditor(context) { var taginfo = services.taginfo; var _entityID; + var _contentSelection = d3_select(null); function downloadMember(d) { d3_event.preventDefault(); // display the loading indicator d3_select(this.parentNode).classed('tag-reference-loading', true); - context.loadEntity(d.id); + context.loadEntity(d.id, function() { + updateDisclosureContent(_contentSelection); + }); } function zoomToMember(d) { @@ -87,11 +90,11 @@ export function uiRawMemberEditor(context) { } } + function updateDisclosureContent(selection) { + _contentSelection = selection; - function rawMemberEditor(selection) { - var entity = context.entity(_entityID); var memberships = []; - + var entity = context.entity(_entityID); entity.members.slice(0, 1000).forEach(function(member, index) { memberships.push({ index: index, @@ -103,9 +106,269 @@ export function uiRawMemberEditor(context) { }); }); + var list = selection.selectAll('.member-list') + .data([0]); + + list = list.enter() + .append('ul') + .attr('class', 'member-list') + .merge(list); + + + var items = list.selectAll('li') + .data(memberships, function(d) { + return osmEntity.key(d.relation) + ',' + d.index + ',' + + (d.member ? osmEntity.key(d.member) : 'incomplete'); + }); + + items.exit() + .each(unbind) + .remove(); + + var itemsEnter = items.enter() + .append('li') + .attr('class', 'member-row form-field') + .classed('member-incomplete', function(d) { return !d.member; }); + + itemsEnter + .each(function(d) { + var item = d3_select(this); + + var label = item + .append('label') + .attr('class', 'field-label'); + + if (d.member) { + // highlight the member feature in the map while hovering on the list item + item + .on('mouseover', function() { + utilHighlightEntities([d.id], true, context); + }) + .on('mouseout', function() { + utilHighlightEntities([d.id], false, context); + }); + + var labelLink = label + .append('span') + .attr('class', 'label-text') + .append('a') + .attr('href', '#') + .on('click', selectMember); + + labelLink + .append('span') + .attr('class', 'member-entity-type') + .text(function(d) { + var matched = context.presets().match(d.member, context.graph()); + return (matched && matched.name()) || utilDisplayType(d.member.id); + }); + + labelLink + .append('span') + .attr('class', 'member-entity-name') + .text(function(d) { return utilDisplayName(d.member); }); + + label + .append('button') + .attr('class', 'member-zoom') + .attr('title', t('icons.zoom_to')) + .attr('tabindex', -1) + .call(svgIcon('#iD-icon-geolocate')) + .on('click', zoomToMember); + + } else { + var labelText = label + .append('span') + .attr('class', 'label-text'); + + labelText + .append('span') + .attr('class', 'member-entity-type') + .text(t('inspector.' + d.type, { id: d.id })); + + labelText + .append('span') + .attr('class', 'member-entity-name') + .text(t('inspector.incomplete', { id: d.id })); + + label + .append('button') + .attr('class', 'member-download') + .attr('title', t('icons.download')) + .attr('tabindex', -1) + .call(svgIcon('#iD-icon-load')) + .on('click', downloadMember); + } + }); + + var wrapEnter = itemsEnter + .append('div') + .attr('class', 'form-field-input-wrap form-field-input-member'); + + wrapEnter + .append('input') + .attr('class', 'member-role') + .property('type', 'text') + .attr('maxlength', 255) + .attr('placeholder', t('inspector.role')) + .call(utilNoAuto); + + wrapEnter + .append('button') + .attr('tabindex', -1) + .attr('title', t('icons.remove')) + .attr('class', 'remove form-field-button member-delete') + .call(svgIcon('#iD-operation-delete')); + + if (taginfo) { + wrapEnter.each(bindTypeahead); + } + + var dragOrigin, targetIndex; + + itemsEnter.call(d3_drag() + .on('start', function() { + dragOrigin = { + x: d3_event.x, + y: d3_event.y + }; + targetIndex = null; + }) + .on('drag', function(d, index) { + var x = d3_event.x - dragOrigin.x, + y = d3_event.y - dragOrigin.y; + + if (!d3_select(this).classed('dragging') && + // don't display drag until dragging beyond a distance threshold + Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= 5) return; + + d3_select(this) + .classed('dragging', true); + + targetIndex = null; + + selection.selectAll('li.member-row') + .style('transform', function(d2, index2) { + var node = d3_select(this).node(); + if (index === index2) { + return 'translate(' + x + 'px, ' + y + 'px)'; + } else if (index2 > index && d3_event.y > node.offsetTop - node.offsetHeight) { + if (targetIndex === null || index2 > targetIndex) { + targetIndex = index2; + } + return 'translateY(-100%)'; + } else if (index2 < index && d3_event.y < node.offsetTop) { + if (targetIndex === null || index2 < targetIndex) { + targetIndex = index2; + } + return 'translateY(100%)'; + } + return null; + }); + }) + .on('end', function(d, index) { + + if (!d3_select(this).classed('dragging')) { + return; + } + + d3_select(this) + .classed('dragging', false); + + selection.selectAll('li.member-row') + .style('transform', null); + + if (targetIndex !== null) { + // dragged to a new position, reorder + context.perform( + actionMoveMember(d.relation.id, index, targetIndex), + t('operations.reorder_members.annotation') + ); + } + }) + ); + + + // update + items = items + .merge(itemsEnter); + + items.select('input.member-role') + .property('value', function(d) { return d.role; }) + .on('blur', changeRole) + .on('change', changeRole); + + items.select('button.member-delete') + .on('click', deleteMember); + + + + function bindTypeahead(d) { + var row = d3_select(this); + var role = row.selectAll('input.member-role'); + var origValue = role.property('value'); + + function sort(value, data) { + var sameletter = []; + var other = []; + for (var i = 0; i < data.length; i++) { + if (data[i].value.substring(0, value.length) === value) { + sameletter.push(data[i]); + } else { + other.push(data[i]); + } + } + return sameletter.concat(other); + } + + role.call(uiCombobox(context, 'member-role') + .fetcher(function(role, callback) { + // The `geometry` param is used in the `taginfo.js` interface for + // filtering results, as a key into the `tag_members_fractions` + // object. If we don't know the geometry because the member is + // not yet downloaded, it's ok to guess based on type. + var geometry; + if (d.member) { + geometry = context.geometry(d.member.id); + } else if (d.type === 'relation') { + geometry = 'relation'; + } else if (d.type === 'way') { + geometry = 'line'; + } else { + geometry = 'point'; + } + + var rtype = entity.tags.type; + taginfo.roles({ + debounce: true, + rtype: rtype || '', + geometry: geometry, + query: role + }, function(err, data) { + if (!err) callback(sort(role, data)); + }); + }) + .on('cancel', function() { + role.property('value', origValue); + }) + ); + } + + + function unbind() { + var row = d3_select(this); + + row.selectAll('input.member-role') + .call(uiCombobox.off); + } + } + + function rawMemberEditor(selection) { + var entity = context.entity(_entityID); + var gt = entity.members.length > 1000 ? '>' : ''; selection.call(uiDisclosure(context, 'raw_member_editor', true) - .title(t('inspector.all_members') + ' (' + gt + memberships.length + ')') + .title(t('inspector.all_members') + ' (' + gt + entity.members.slice(0, 1000).length + ')') .expanded(true) .updatePreference(false) .on('toggled', function(expanded) { @@ -113,270 +376,10 @@ export function uiRawMemberEditor(context) { selection.node().parentNode.scrollTop += 200; } }) - .content(content) + .content(updateDisclosureContent) ); - - - function content(selection) { - var list = selection.selectAll('.member-list') - .data([0]); - - list = list.enter() - .append('ul') - .attr('class', 'member-list') - .merge(list); - - - var items = list.selectAll('li') - .data(memberships, function(d) { - return osmEntity.key(d.relation) + ',' + d.index + ',' + - (d.member ? osmEntity.key(d.member) : 'incomplete'); - }); - - items.exit() - .each(unbind) - .remove(); - - var itemsEnter = items.enter() - .append('li') - .attr('class', 'member-row form-field') - .classed('member-incomplete', function(d) { return !d.member; }); - - itemsEnter - .each(function(d) { - var item = d3_select(this); - - var label = item - .append('label') - .attr('class', 'field-label'); - - if (d.member) { - // highlight the member feature in the map while hovering on the list item - item - .on('mouseover', function() { - utilHighlightEntities([d.id], true, context); - }) - .on('mouseout', function() { - utilHighlightEntities([d.id], false, context); - }); - - var labelLink = label - .append('span') - .attr('class', 'label-text') - .append('a') - .attr('href', '#') - .on('click', selectMember); - - labelLink - .append('span') - .attr('class', 'member-entity-type') - .text(function(d) { - var matched = context.presets().match(d.member, context.graph()); - return (matched && matched.name()) || utilDisplayType(d.member.id); - }); - - labelLink - .append('span') - .attr('class', 'member-entity-name') - .text(function(d) { return utilDisplayName(d.member); }); - - label - .append('button') - .attr('class', 'member-zoom') - .attr('title', t('icons.zoom_to')) - .attr('tabindex', -1) - .call(svgIcon('#iD-icon-geolocate')) - .on('click', zoomToMember); - - } else { - var labelText = label - .append('span') - .attr('class', 'label-text'); - - labelText - .append('span') - .attr('class', 'member-entity-type') - .text(t('inspector.' + d.type, { id: d.id })); - - labelText - .append('span') - .attr('class', 'member-entity-name') - .text(t('inspector.incomplete', { id: d.id })); - - label - .append('button') - .attr('class', 'member-download') - .attr('title', t('icons.download')) - .attr('tabindex', -1) - .call(svgIcon('#iD-icon-load')) - .on('click', downloadMember); - } - }); - - var wrapEnter = itemsEnter - .append('div') - .attr('class', 'form-field-input-wrap form-field-input-member'); - - wrapEnter - .append('input') - .attr('class', 'member-role') - .property('type', 'text') - .attr('maxlength', 255) - .attr('placeholder', t('inspector.role')) - .call(utilNoAuto); - - wrapEnter - .append('button') - .attr('tabindex', -1) - .attr('title', t('icons.remove')) - .attr('class', 'remove form-field-button member-delete') - .call(svgIcon('#iD-operation-delete')); - - if (taginfo) { - wrapEnter.each(bindTypeahead); - } - - var dragOrigin, targetIndex; - - itemsEnter.call(d3_drag() - .on('start', function() { - dragOrigin = { - x: d3_event.x, - y: d3_event.y - }; - targetIndex = null; - }) - .on('drag', function(d, index) { - var x = d3_event.x - dragOrigin.x, - y = d3_event.y - dragOrigin.y; - - if (!d3_select(this).classed('dragging') && - // don't display drag until dragging beyond a distance threshold - Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= 5) return; - - d3_select(this) - .classed('dragging', true); - - targetIndex = null; - - selection.selectAll('li.member-row') - .style('transform', function(d2, index2) { - var node = d3_select(this).node(); - if (index === index2) { - return 'translate(' + x + 'px, ' + y + 'px)'; - } else if (index2 > index && d3_event.y > node.offsetTop - node.offsetHeight) { - if (targetIndex === null || index2 > targetIndex) { - targetIndex = index2; - } - return 'translateY(-100%)'; - } else if (index2 < index && d3_event.y < node.offsetTop) { - if (targetIndex === null || index2 < targetIndex) { - targetIndex = index2; - } - return 'translateY(100%)'; - } - return null; - }); - }) - .on('end', function(d, index) { - - if (!d3_select(this).classed('dragging')) { - return; - } - - d3_select(this) - .classed('dragging', false); - - selection.selectAll('li.member-row') - .style('transform', null); - - if (targetIndex !== null) { - // dragged to a new position, reorder - context.perform( - actionMoveMember(d.relation.id, index, targetIndex), - t('operations.reorder_members.annotation') - ); - } - }) - ); - - - // update - items = items - .merge(itemsEnter); - - items.select('input.member-role') - .property('value', function(d) { return d.role; }) - .on('blur', changeRole) - .on('change', changeRole); - - items.select('button.member-delete') - .on('click', deleteMember); - - - - function bindTypeahead(d) { - var row = d3_select(this); - var role = row.selectAll('input.member-role'); - var origValue = role.property('value'); - - function sort(value, data) { - var sameletter = []; - var other = []; - for (var i = 0; i < data.length; i++) { - if (data[i].value.substring(0, value.length) === value) { - sameletter.push(data[i]); - } else { - other.push(data[i]); - } - } - return sameletter.concat(other); - } - - role.call(uiCombobox(context, 'member-role') - .fetcher(function(role, callback) { - // The `geometry` param is used in the `taginfo.js` interface for - // filtering results, as a key into the `tag_members_fractions` - // object. If we don't know the geometry because the member is - // not yet downloaded, it's ok to guess based on type. - var geometry; - if (d.member) { - geometry = context.geometry(d.member.id); - } else if (d.type === 'relation') { - geometry = 'relation'; - } else if (d.type === 'way') { - geometry = 'line'; - } else { - geometry = 'point'; - } - - var rtype = entity.tags.type; - taginfo.roles({ - debounce: true, - rtype: rtype || '', - geometry: geometry, - query: role - }, function(err, data) { - if (!err) callback(sort(role, data)); - }); - }) - .on('cancel', function() { - role.property('value', origValue); - }) - ); - } - - - function unbind() { - var row = d3_select(this); - - row.selectAll('input.member-role') - .call(uiCombobox.off); - } - } } - rawMemberEditor.entityID = function(val) { if (!arguments.length) return _entityID; _entityID = val;