UNPKG

mapeo-id-bmf

Version:

iD Editor for osm-p2p & mapeo-desktop changed to meet requirements of Bruno Manser Fonds

383 lines (298 loc) 11.4 kB
import _map from 'lodash-es/map'; import { ascending as d3_ascending } from 'd3-array'; import { dispatch as d3_dispatch } from 'd3-dispatch'; import { event as d3_event, select as d3_select } from 'd3-selection'; import { d3combobox as d3_combobox } from '../lib/d3.combobox.js'; import { t } from '../util/locale'; import { services } from '../services'; import { svgIcon } from '../svg'; import { uiDisclosure } from './disclosure'; import { uiTagReference } from './tag_reference'; import { utilGetSetValue, utilNoAuto, utilRebind } from '../util'; export function uiRawTagEditor(context) { var taginfo = services.taginfo, dispatch = d3_dispatch('change'), _readOnlyTags = [], _showBlank = false, _updatePreference = true, _expanded = false, _newRow, _state, _preset, _tags, _entityID; function rawTagEditor(selection) { var count = Object.keys(_tags).filter(function(d) { return d; }).length; var disclosure = uiDisclosure(context, 'raw_tag_editor', false) .title(t('inspector.all_tags') + ' (' + count + ')') .on('toggled', toggled) .updatePreference(_updatePreference) .content(content); // Sometimes we want to force the raw_tag_editor to be opened/closed.. // When undefined, uiDisclosure will use the user's stored preference. if (_expanded !== undefined) { disclosure.expanded(_expanded); } selection.call(disclosure); function toggled(expanded) { _expanded = expanded; if (expanded) { selection.node().parentNode.scrollTop += 200; } } } function content(wrap) { var entries = _map(_tags, function(v, k) { return { key: k, value: v }; }); if (!entries.length || _showBlank) { _showBlank = false; entries.push({key: '', value: ''}); _newRow = ''; } var list = wrap.selectAll('.tag-list') .data([0]); list = list.enter() .append('ul') .attr('class', 'tag-list') .merge(list); var newTag = wrap.selectAll('.add-tag') .data([0]); newTag.enter() .append('button') .attr('class', 'add-tag') .on('click', addTag) .call(svgIcon('#icon-plus', 'light')); var items = list.selectAll('.tag-row') .data(entries, function(d) { return d.key; }); items.exit() .each(unbind) .remove(); // Enter var enter = items.enter() .append('li') .attr('class', 'tag-row cf') .classed('readonly', isReadOnly); enter .append('div') .attr('class', 'key-wrap') .append('input') .property('type', 'text') .attr('class', 'key') .call(utilNoAuto) .on('blur', keyChange) .on('change', keyChange); enter .append('div') .attr('class', 'input-wrap-position') .append('input') .property('type', 'text') .attr('class', 'value') .call(utilNoAuto) .on('blur', valueChange) .on('change', valueChange) .on('keydown.push-more', pushMore); enter .append('button') .attr('tabindex', -1) .attr('class', 'remove minor') .call(svgIcon('#operation-delete')); // Update items = items .merge(enter) .sort(function(a, b) { return (a.key === _newRow && b.key !== _newRow) ? 1 : (a.key !== _newRow && b.key === _newRow) ? -1 : d3_ascending(a.key, b.key); }); items .each(function(tag) { var row = d3_select(this), key = row.select('input.key'), // propagate bound data to child value = row.select('input.value'); // propagate bound data to child if (_entityID && taginfo) { bindTypeahead(key, value); } var isRelation = (_entityID && context.entity(_entityID).type === 'relation'), reference; if (isRelation && tag.key === 'type') { reference = uiTagReference({ rtype: tag.value }, context); } else { reference = uiTagReference({ key: tag.key, value: tag.value }, context); } if (_state === 'hover') { reference.showing(false); } row .call(reference.button) .call(reference.body); }); items.selectAll('input.key') .attr('title', function(d) { return d.key; }) .call(utilGetSetValue, function(d) { return d.key; }) .property('disabled', isReadOnly); items.selectAll('input.value') .attr('title', function(d) { return d.value; }) .call(utilGetSetValue, function(d) { return d.value; }) .property('disabled', isReadOnly); items.selectAll('button.remove') .on('click', removeTag); function isReadOnly(d) { for (var i = 0; i < _readOnlyTags.length; i++) { if (d.key.match(_readOnlyTags[i]) !== null) { return true; } } return false; } function pushMore() { if (d3_event.keyCode === 9 && !d3_event.shiftKey && list.selectAll('li:last-child input.value').node() === this) { addTag(); } } function bindTypeahead(key, value) { if (isReadOnly({ key: key })) return; var geometry = context.geometry(_entityID); key.call(d3_combobox() .container(context.container()) .fetcher(function(value, callback) { taginfo.keys({ debounce: true, geometry: geometry, query: value }, function(err, data) { if (!err) callback(sort(value, data)); }); })); value.call(d3_combobox() .container(context.container()) .fetcher(function(value, callback) { taginfo.values({ debounce: true, key: utilGetSetValue(key), geometry: geometry, query: value }, function(err, data) { if (!err) callback(sort(value, data)); }); })); function sort(value, data) { var sameletter = [], 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); } } function unbind() { var row = d3_select(this); row.selectAll('input.key') .call(d3_combobox.off); row.selectAll('input.value') .call(d3_combobox.off); } function keyChange(d) { var kOld = d.key, kNew = this.value.trim(), tag = {}; if (isReadOnly({ key: kNew })) { this.value = kOld; return; } if (kNew && kNew !== kOld) { var match = kNew.match(/^(.*?)(?:_(\d+))?$/), base = match[1], suffix = +(match[2] || 1); while (_tags[kNew]) { // rename key if already in use kNew = base + '_' + suffix++; } } tag[kOld] = undefined; tag[kNew] = d.value; d.key = kNew; // Maintain DOM identity through the subsequent update. if (_newRow === kOld) { // see if this row is still a new row _newRow = ((d.value === '' || kNew === '') ? kNew : undefined); } this.value = kNew; dispatch.call('change', this, tag); } function valueChange(d) { if (isReadOnly(d)) return; var tag = {}; tag[d.key] = this.value; if (_newRow === d.key && d.key !== '' && d.value !== '') { // not a new row anymore _newRow = undefined; } dispatch.call('change', this, tag); } function removeTag(d) { if (isReadOnly(d)) return; var tag = {}; tag[d.key] = undefined; dispatch.call('change', this, tag); d3_select(this.parentNode).remove(); } function addTag() { // Wrapped in a setTimeout in case it's being called from a blur // handler. Without the setTimeout, the call to `content` would // wipe out the pending value change. window.setTimeout(function() { _showBlank = true; content(wrap); list.selectAll('li:last-child input.key').node().focus(); }, 1); } } rawTagEditor.state = function(_) { if (!arguments.length) return _state; _state = _; return rawTagEditor; }; rawTagEditor.preset = function(_) { if (!arguments.length) return _preset; _preset = _; if (_preset.isFallback()) { _expanded = true; _updatePreference = false; } else { _expanded = undefined; _updatePreference = true; } return rawTagEditor; }; rawTagEditor.tags = function(_) { if (!arguments.length) return _tags; _tags = _; return rawTagEditor; }; rawTagEditor.entityID = function(_) { if (!arguments.length) return _entityID; _entityID = _; return rawTagEditor; }; rawTagEditor.expanded = function(_) { if (!arguments.length) return _expanded; _expanded = _; _updatePreference = false; return rawTagEditor; }; rawTagEditor.readOnlyTags = function(_) { if (!arguments.length) return _readOnlyTags; _readOnlyTags = _; return rawTagEditor; }; return utilRebind(rawTagEditor, dispatch, 'on'); }