Add initial multiselection raw tag editing in 2.x

This commit is contained in:
Quincy Morgan
2020-01-17 17:49:26 -05:00
parent ba40154a06
commit eda51f6835
10 changed files with 500 additions and 288 deletions
+1 -7
View File
@@ -1245,18 +1245,12 @@ a.hide-toggle {
display: flex;
flex-flow: row wrap;
justify-content: flex-end;
padding: 0 20px;
padding: 5px 0 0 0;
}
.quick-link {
margin: 0 5px;
}
.data-editor .quick-links,
.error-editor .quick-links,
.note-editor .quick-links {
padding: 5px 0 0 0;
}
/* Entity/Preset Editor
------------------------------------------------------- */
+2
View File
@@ -555,6 +555,7 @@ en:
edit_reference: "edit/translate"
wiki_reference: View documentation
wiki_en_reference: View documentation in English
multiple_values: Multiple Values
hidden_preset:
manual: "{features} are hidden. Enable them in the Map Data pane."
zoom: "{features} are hidden. Zoom in to enable them."
@@ -566,6 +567,7 @@ en:
incomplete: <not downloaded>
feature_list: Search features
edit: Edit feature
edit_features: Edit features
check:
"yes": "Yes"
"no": "No"
+2
View File
@@ -698,6 +698,7 @@
"edit_reference": "edit/translate",
"wiki_reference": "View documentation",
"wiki_en_reference": "View documentation in English",
"multiple_values": "Multiple Values",
"hidden_preset": {
"manual": "{features} are hidden. Enable them in the Map Data pane.",
"zoom": "{features} are hidden. Zoom in to enable them."
@@ -710,6 +711,7 @@
"incomplete": "<not downloaded>",
"feature_list": "Search features",
"edit": "Edit feature",
"edit_features": "Edit features",
"check": {
"yes": "Yes",
"no": "No",
+1 -7
View File
@@ -19,7 +19,6 @@ import { modeDragNote } from './drag_note';
import { osmNode, osmWay } from '../osm';
import * as Operations from '../operations/index';
import { uiEditMenu } from '../ui/edit_menu';
import { uiSelectionList } from '../ui/selection_list';
import { uiCmd } from '../ui/cmd';
import {
utilArrayIntersection, utilDeepMemberSelector, utilEntityOrDeepMemberSelector,
@@ -307,7 +306,7 @@ export function modeSelect(context, selectedIDs) {
.call(keybinding);
context.ui().sidebar
.select(singular() ? singular().id : null, _newFeature);
.select(selectedIDs, _newFeature);
context.history()
.on('change.select', function() {
@@ -332,11 +331,6 @@ export function modeSelect(context, selectedIDs) {
selectElements();
if (selectedIDs.length > 1) {
var entities = uiSelectionList(context, selectedIDs);
context.ui().sidebar.show(entities);
}
if (_follow) {
var extent = geoExtent();
var graph = context.graph();
+301 -207
View File
@@ -1,5 +1,5 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import {event as d3_event, selectAll as d3_selectAll } from 'd3-selection';
import { event as d3_event, selectAll as d3_selectAll, select as d3_select } from 'd3-selection';
import deepEqual from 'fast-deep-equal';
import { t, textDirection } from '../util/locale';
@@ -15,6 +15,7 @@ import { uiRawTagEditor } from './raw_tag_editor';
import { uiTagReference } from './tag_reference';
import { uiPresetEditor } from './preset_editor';
import { uiEntityIssues } from './entity_issues';
import { uiSelectionList } from './selection_list';
import { uiTooltipHtml } from './tooltipHtml';
import { utilCleanTags, utilRebind } from '../util';
@@ -24,12 +25,13 @@ export function uiEntityEditor(context) {
var _state = 'select';
var _coalesceChanges = false;
var _modified = false;
var _scrolled = false;
var _base;
var _entityID;
var _entityIDs;
var _activePreset;
var _tagReference;
var _newFeature;
var selectionList = uiSelectionList(context);
var entityIssues = uiEntityIssues(context);
var quickLinks = uiQuickLinks();
var presetEditor = uiPresetEditor(context).on('change', changeTags);
@@ -38,8 +40,9 @@ export function uiEntityEditor(context) {
var rawMembershipEditor = uiRawMembershipEditor(context);
function entityEditor(selection) {
var entity = context.entity(_entityID);
var tags = Object.assign({}, entity.tags); // shallow copy
var entityID = singularEntityID();
var entity = entityID && context.entity(entityID);
var tags = entity && Object.assign({}, entity.tags); // shallow copy
// Header
var header = selection.selectAll('.header')
@@ -62,19 +65,21 @@ export function uiEntityEditor(context) {
.call(svgIcon(_modified ? '#iD-icon-apply' : '#iD-icon-close'));
headerEnter
.append('h3')
.text(t('inspector.edit'));
.append('h3');
// Update
header = header
.merge(headerEnter);
header.selectAll('h3')
.text(entityID ? t('inspector.edit') : t('inspector.edit_features'));
header.selectAll('.preset-reset')
.style('display', entityID ? null : 'none')
.on('click', function() {
dispatch.call('choose', this, _activePreset);
});
// Body
var body = selection.selectAll('.inspector-body')
.data([0]);
@@ -82,159 +87,215 @@ export function uiEntityEditor(context) {
// Enter
var bodyEnter = body.enter()
.append('div')
.attr('class', 'inspector-body')
.on('scroll.entity-editor', function() { _scrolled = true; });
bodyEnter
.append('div')
.attr('class', 'preset-list-item inspector-inner')
.append('div')
.attr('class', 'preset-list-button-wrap')
.append('button')
.attr('class', 'preset-list-button preset-reset')
.call(tooltip().title(t('inspector.back_tooltip')).placement('bottom'))
.append('div')
.attr('class', 'label')
.append('div')
.attr('class', 'label-inner');
bodyEnter
.append('div')
.attr('class', 'preset-quick-links');
bodyEnter
.append('div')
.attr('class', 'entity-issues');
bodyEnter
.append('div')
.attr('class', 'preset-editor');
bodyEnter
.append('div')
.attr('class', 'raw-tag-editor inspector-inner');
bodyEnter
.append('div')
.attr('class', 'raw-member-editor inspector-inner');
bodyEnter
.append('div')
.attr('class', 'raw-membership-editor inspector-inner');
bodyEnter
.append('input')
.attr('type', 'text')
.attr('class', 'key-trap');
.attr('class', 'entity-editor inspector-body sep-top');
// Update
body = body
.merge(bodyEnter);
// update header
if (_tagReference) {
body.selectAll('.preset-list-button-wrap')
.call(_tagReference.button);
body.selectAll('.preset-list-item')
.call(_tagReference.body);
}
body.selectAll('.preset-reset')
.on('click', function() {
dispatch.call('choose', this, _activePreset);
});
body.select('.preset-list-item button')
.call(uiPresetIcon(context)
.geometry(context.geometry(_entityID))
.preset(_activePreset)
);
// NOTE: split on en-dash, not a hypen (to avoid conflict with hyphenated names)
var label = body.select('.label-inner');
var nameparts = label.selectAll('.namepart')
.data(_activePreset.name().split(' '), function(d) { return d; });
nameparts.exit()
.remove();
nameparts
.enter()
.append('div')
.attr('class', 'namepart')
.text(function(d) { return d; });
// update quick links
var choices = [{
id: 'zoom_to',
label: 'inspector.zoom_to.title',
tooltip: function() {
return uiTooltipHtml(t('inspector.zoom_to.tooltip_feature'), t('inspector.zoom_to.key'));
},
click: function zoomTo() {
context.mode().zoomToSelected();
}
}];
body.select('.preset-quick-links')
.call(quickLinks.choices(choices));
// update editor sections
body.select('.entity-issues')
.call(entityIssues
.entityID(_entityID)
);
body.select('.preset-editor')
.call(presetEditor
.preset(_activePreset)
.entityID(_entityID)
.tags(tags)
.state(_state)
);
body.select('.raw-tag-editor')
.call(rawTagEditor
.preset(_activePreset)
.entityID(_entityID)
.tags(tags)
.state(_state)
);
if (entity.type === 'relation') {
body.select('.raw-member-editor')
.style('display', 'block')
.call(rawMemberEditor
.entityID(_entityID)
);
} else {
body.select('.raw-member-editor')
.style('display', 'none');
}
body.select('.raw-membership-editor')
.call(rawMembershipEditor
.entityID(_entityID)
);
body.select('.key-trap')
.on('keydown.key-trap', function() {
// On tabbing, send focus back to the first field on the inspector-body
// (probably the `name` field) #4159
if (d3_event.keyCode === 9 && !d3_event.shiftKey) {
d3_event.preventDefault();
body.select('input').node().focus();
var sectionInfos = [
{
klass: 'selection-list',
shouldHave: _entityIDs.length > 1,
update: function(section) {
section
.call(selectionList
.setSelectedIDs(_entityIDs)
);
}
},
{
klass: 'preset-list-item inspector-inner',
shouldHave: entityID,
create: function(sectionEnter) {
var presetButtonWrap = sectionEnter
.append('div')
.attr('class', 'preset-list-button-wrap');
var presetButton = presetButtonWrap.append('button')
.attr('class', 'preset-list-button preset-reset')
.call(tooltip().title(t('inspector.back_tooltip')).placement('bottom'));
presetButton
.append('div')
.attr('class', 'label')
.append('div')
.attr('class', 'label-inner');
presetButtonWrap.append('div')
.attr('class', 'accessory-buttons');
// update quick links
var choices = [{
id: 'zoom_to',
label: 'inspector.zoom_to.title',
tooltip: function() {
return uiTooltipHtml(t('inspector.zoom_to.tooltip_feature'), t('inspector.zoom_to.key'));
},
click: function zoomTo() {
context.mode().zoomToSelected();
}
}];
sectionEnter
.append('div')
.attr('class', 'preset-quick-links')
.call(quickLinks.choices(choices));
},
update: function(section) {
// update header
if (_tagReference) {
section.selectAll('.preset-list-button-wrap .accessory-buttons')
.call(_tagReference.button);
section.selectAll('.preset-list-item')
.call(_tagReference.body);
}
section.selectAll('.preset-reset')
.on('click', function() {
dispatch.call('choose', this, _activePreset);
})
.on('mousedown', function() {
d3_event.preventDefault();
d3_event.stopPropagation();
})
.on('mouseup', function() {
d3_event.preventDefault();
d3_event.stopPropagation();
});
section.select('.preset-list-item button')
.call(uiPresetIcon(context)
.geometry(context.geometry(entityID))
.preset(_activePreset)
);
// NOTE: split on en-dash, not a hypen (to avoid conflict with hyphenated names)
var label = section.select('.label-inner');
var nameparts = label.selectAll('.namepart')
.data(_activePreset.name().split(' '), function(d) { return d; });
nameparts.exit()
.remove();
nameparts
.enter()
.append('div')
.attr('class', 'namepart')
.text(function(d) { return d; });
}
}, {
klass: 'entity-issues',
shouldHave: entityID,
update: function(section) {
section
.call(entityIssues
.entityID(entityID)
);
}
}, {
klass: 'preset-editor',
shouldHave: entityID,
update: function(section) {
section
.call(presetEditor
.preset(_activePreset)
.entityID(entityID)
.tags(tags)
.state(_state)
);
}
}, {
klass: 'raw-tag-editor inspector-inner',
shouldHave: true,
update: function(section) {
section
.call(rawTagEditor
.preset(_activePreset)
.entityIDs(_entityIDs)
.state(_state)
);
}
}, {
klass: 'raw-member-editor inspector-inner',
shouldHave: entity && entity.type === 'relation',
update: function(section) {
section
.call(rawMemberEditor
.entityID(entityID)
);
}
}, {
klass: 'raw-membership-editor inspector-inner',
shouldHave: entityID,
update: function(section) {
section
.call(rawMembershipEditor
.entityID(entityID)
);
}
}, {
klass: 'key-trap-wrap',
shouldHave: true,
create: function(sectionEnter) {
sectionEnter
.append('input')
.attr('type', 'text')
.attr('class', 'key-trap');
},
update: function(section) {
section.select('key-trap')
.on('keydown.key-trap', function() {
// On tabbing, send focus back to the first field on the inspector-body
// (probably the `name` field) #4159
if (d3_event.keyCode === 9 && !d3_event.shiftKey) {
d3_event.preventDefault();
body.select('input').node().focus();
}
});
}
}
];
sectionInfos = sectionInfos.filter(function(info) {
return info.shouldHave;
});
var sections = body.selectAll('.section')
.data(sectionInfos, function(d) { return d.klass; });
sections.exit().remove();
var sectionsEnter = sections.enter()
.append('div')
.attr('class', function(d) {
return 'section ' + d.klass;
});
sectionsEnter.each(function(d) {
if (d.create) {
d.create(d3_select(this));
}
});
sections = sectionsEnter
.merge(sections);
sections.each(function(d) {
if (d.update) {
d.update(d3_select(this));
}
});
context.history()
.on('change.entity-editor', historyChanged);
function historyChanged(difference) {
if (selection.selectAll('.entity-editor').empty()) return;
if (_state === 'hide') return;
var significant = !difference ||
difference.didChange.properties ||
@@ -242,30 +303,12 @@ export function uiEntityEditor(context) {
difference.didChange.deletion;
if (!significant) return;
var entity = context.hasEntity(_entityID);
_entityIDs = _entityIDs.filter(context.hasEntity);
if (!_entityIDs.length) return;
loadActivePreset();
var graph = context.graph();
if (!entity) return;
var match = context.presets().match(entity, graph);
var activePreset = entityEditor.preset();
var weakPreset = activePreset &&
Object.keys(activePreset.addTags || {}).length === 0;
// A "weak" preset doesn't set any tags. (e.g. "Address")
// Don't replace a weak preset with a fallback preset (e.g. "Point")
if (!(weakPreset && match.isFallback())) {
entityEditor.preset(match);
if (match.id !== activePreset.id) {
// flash the button to indicate the preset changed
selection
.selectAll('button.preset-reset .label')
.style('background-color', '#fff')
.transition()
.duration(500)
.style('background-color', null);
}
}
entityEditor.modified(_base !== graph);
entityEditor(selection);
}
@@ -275,27 +318,45 @@ export function uiEntityEditor(context) {
// Tag changes that fire on input can all get coalesced into a single
// history operation when the user leaves the field. #2342
function changeTags(changed, onInput) {
var entity = context.entity(_entityID);
var annotation = t('operations.change_tags.annotation');
var tags = Object.assign({}, entity.tags); // shallow copy
for (var k in changed) {
if (!k) continue;
var v = changed[k];
if (v !== undefined || tags.hasOwnProperty(k)) {
tags[k] = v;
var actions = [];
for (var i in _entityIDs) {
var entityID = _entityIDs[i];
var entity = context.entity(entityID);
var tags = Object.assign({}, entity.tags); // shallow copy
for (var k in changed) {
if (!k) continue;
var v = changed[k];
if (v !== undefined || tags.hasOwnProperty(k)) {
tags[k] = v;
}
}
if (!onInput) {
tags = utilCleanTags(tags);
}
if (!deepEqual(entity.tags, tags)) {
actions.push(actionChangeTags(entityID, tags));
}
}
if (!onInput) {
tags = utilCleanTags(tags);
}
if (actions.length) {
var combinedAction = function(graph) {
actions.forEach(function(action) {
graph = action(graph);
});
return graph;
};
var annotation = t('operations.change_tags.annotation');
if (!deepEqual(entity.tags, tags)) {
if (_coalesceChanges) {
context.overwrite(actionChangeTags(_entityID, tags), annotation);
context.overwrite(combinedAction, annotation);
} else {
context.perform(actionChangeTags(_entityID, tags), annotation);
context.perform(combinedAction, annotation);
_coalesceChanges = !!onInput;
}
}
@@ -310,8 +371,6 @@ export function uiEntityEditor(context) {
entityEditor.modified = function(val) {
if (!arguments.length) return _modified;
_modified = val;
d3_selectAll('button.preset-close use')
.attr('xlink:href', (_modified ? '#iD-icon-apply' : '#iD-icon-close'));
return entityEditor;
};
@@ -323,39 +382,74 @@ export function uiEntityEditor(context) {
};
entityEditor.entityID = function(val) {
if (!arguments.length) return _entityID;
if (_entityID === val) return entityEditor; // exit early if no change
entityEditor.entityIDs = function(val) {
if (!arguments.length) return _entityIDs;
if (_entityIDs === val) return entityEditor; // exit early if no change
_entityID = val;
_entityIDs = val;
_base = context.graph();
_coalesceChanges = false;
// reset the scroll to the top of the inspector (warning: triggers reflow)
if (_scrolled) {
window.requestIdleCallback(function() {
var body = d3_selectAll('.entity-editor-pane .inspector-body');
if (!body.empty()) {
_scrolled = false;
body.node().scrollTop = 0;
}
});
}
var presetMatch = context.presets().match(context.entity(_entityID), _base);
loadActivePreset();
return entityEditor
.preset(presetMatch)
.modified(false);
};
entityEditor.newFeature = function(val) {
if (!arguments.length) return _newFeature;
_newFeature = val;
return entityEditor;
};
function singularEntityID() {
if (_entityIDs.length === 1) {
return _entityIDs[0];
}
return null;
}
function loadActivePreset() {
var entityID = singularEntityID();
var entity = entityID && context.hasEntity(entityID);
if (!entity) return;
var graph = context.graph();
var match = context.presets().match(entity, graph);
// A "weak" preset doesn't set any tags. (e.g. "Address")
var weakPreset = _activePreset &&
Object.keys(_activePreset.addTags || {}).length === 0;
// Don't replace a weak preset with a fallback preset (e.g. "Point")
if ((weakPreset && match.isFallback()) ||
// don't reload for same preset
match === _activePreset) return;
if (_activePreset && match.id !== _activePreset.id) {
// flash the button to indicate the preset changed
d3_selectAll('.entity-editor button.preset-reset .label')
.style('background-color', '#fff')
.transition()
.duration(500)
.style('background-color', null);
}
entityEditor.preset(match);
}
entityEditor.preset = function(val) {
if (!arguments.length) return _activePreset;
if (val !== _activePreset) {
_activePreset = val;
_tagReference = uiTagReference(_activePreset.reference(context.geometry(_entityID)), context)
.showing(false);
var entityID = singularEntityID();
if (entityID) {
_tagReference = uiTagReference(_activePreset.reference(context.geometry(entityID)), context)
.showing(false);
}
}
return entityEditor;
};
+40 -15
View File
@@ -13,19 +13,19 @@ export function uiInspector(context) {
presetPane = d3_select(null),
editorPane = d3_select(null);
var _state = 'select';
var _entityID;
var _entityIDs;
var _newFeature = false;
function inspector(selection, newFeature) {
presetList
.entityID(_entityID)
.entityID(_entityIDs.length === 1 && _entityIDs[0])
.autofocus(_newFeature)
.on('choose', inspector.setPreset);
entityEditor
.state(_state)
.entityID(_entityID)
.entityIDs(_entityIDs)
.on('choose', inspector.showList);
wrap = selection.selectAll('.panewrap')
@@ -47,16 +47,34 @@ export function uiInspector(context) {
presetPane = wrap.selectAll('.preset-list-pane');
editorPane = wrap.selectAll('.entity-editor-pane');
var entity = context.entity(_entityID);
function shouldDefaultToPresetList() {
// can only change preset on single selection
if (_entityIDs.length !== 1) return false;
var hasNonGeometryTags = entity.hasNonGeometryTags();
var isTaglessOrIntersectionVertex = entity.geometry(context.graph()) === 'vertex' &&
(!hasNonGeometryTags && !entity.isHighwayIntersection(context.graph()));
var issues = context.validator().getEntityIssues(_entityID);
// start with the preset list if the feature is new and untagged or is an uninteresting vertex
var showPresetList = (newFeature && !hasNonGeometryTags) || (isTaglessOrIntersectionVertex && !issues.length);
var entityID = _entityIDs[0];
var entity = context.hasEntity(entityID);
if (!entity) return false;
if (showPresetList) {
// default to inspector if there are already tags
if (entity.hasNonGeometryTags()) return false;
// prompt to select preset if feature is new and untagged
if (newFeature) return true;
// all existing features except vertices should default to inspector
if (entity.geometry(context.graph()) !== 'vertex') return false;
// show vertex issues if there are any
if (context.validator().getEntityIssues(entityID).length) return false;
// show turn retriction editor for junction vertices
if (entity.isHighwayIntersection(context.graph())) return false;
// otherwise show preset list for uninteresting vertices
return true;
}
if (shouldDefaultToPresetList()) {
wrap.style('right', '-100%');
presetPane.call(presetList);
} else {
@@ -74,11 +92,18 @@ export function uiInspector(context) {
footer
.call(uiViewOnOSM(context)
.what(context.hasEntity(_entityID))
.what(context.hasEntity(_entityIDs.length === 1 && _entityIDs[0]))
);
}
inspector.showList = function(preset) {
if (!preset) {
if (_entityIDs.length !== 1) return;
preset = context.presets().match(_entityIDs[0], context.graph());
}
wrap.transition()
.styleTween('right', function() { return d3_interpolate('0%', '-100%'); });
@@ -115,9 +140,9 @@ export function uiInspector(context) {
};
inspector.entityID = function(val) {
if (!arguments.length) return _entityID;
_entityID = val;
inspector.entityIDs = function(val) {
if (!arguments.length) return _entityIDs;
_entityIDs = val;
return inspector;
};
+8 -1
View File
@@ -25,6 +25,11 @@ export function uiPresetList(context) {
function presetList(selection) {
if (!_entityID) {
//selection.html('');
return;
}
var entity = context.entity(_entityID);
var geometry = context.geometry(_entityID);
@@ -461,7 +466,9 @@ export function uiPresetList(context) {
presetList.entityID = function(val) {
if (!arguments.length) return _entityID;
_entityID = val;
presetList.preset(context.presets().match(context.entity(_entityID), context.graph()));
if (_entityID) {
presetList.preset(context.presets().match(context.entity(_entityID), context.graph()));
}
return presetList;
};
+115 -16
View File
@@ -22,6 +22,7 @@ export function uiRawTagEditor(context) {
var _readOnlyTags = [];
// the keys in the order we want them to display
var _orderedKeys = [];
var _keyValues = null;
var _showBlank = false;
var _updatePreference = true;
var _expanded = false;
@@ -29,7 +30,7 @@ export function uiRawTagEditor(context) {
var _state;
var _preset;
var _tags;
var _entityID;
var _entityIDs;
function rawTagEditor(selection) {
@@ -89,7 +90,10 @@ export function uiRawTagEditor(context) {
// View Options
var options = wrap.selectAll('.raw-tag-options')
.data([0]);
.data((!_entityIDs || _entityIDs.length === 1) ? [0] : []);
options.exit()
.remove();
var optionsEnter = options.enter()
.append('div')
@@ -237,17 +241,23 @@ export function uiRawTagEditor(context) {
var key = row.select('input.key'); // propagate bound data
var value = row.select('input.value'); // propagate bound data
if (_entityID && taginfo && _state !== 'hover') {
if (_entityIDs && taginfo && _state !== 'hover') {
bindTypeahead(key, value);
}
var isRelation = (_entityID && context.entity(_entityID).type === 'relation');
var reference;
if (isRelation && d.key === 'type') {
reference = uiTagReference({ rtype: d.value }, context);
if (typeof d.value !== 'string') {
reference = uiTagReference({ key: d.key }, context);
} else {
reference = uiTagReference({ key: d.key, value: d.value }, context);
var isRelation = _entityIDs && _entityIDs.some(function(entityID) {
return context.entity(entityID).type === 'relation';
});
if (isRelation && d.key === 'type') {
reference = uiTagReference({ rtype: d.value }, context);
} else {
reference = uiTagReference({ key: d.key, value: d.value }, context);
}
}
if (_state === 'hover') {
@@ -266,12 +276,19 @@ export function uiRawTagEditor(context) {
.attr('title', function(d) { return d.key; })
.call(utilGetSetValue, function(d) { return d.key; })
.attr('readonly', function(d) {
return isReadOnly(d) || null;
return (isReadOnly(d) || (typeof d.value !== 'string')) || null;
});
items.selectAll('input.value')
.attr('title', function(d) { return d.value; })
.call(utilGetSetValue, function(d) { return d.value; })
.attr('title', function(d) {
return typeof d.value === 'string' ? d.value : Array.from(_keyValues[d.key]).sort().join('; ');
})
.attr('placeholder', function(d) {
return typeof d.value === 'string' ? null : t('inspector.multiple_values');
})
.call(utilGetSetValue, function(d) {
return typeof d.value === 'string' ? d.value : '';
})
.attr('readonly', function(d) {
return isReadOnly(d) || null;
});
@@ -376,7 +393,24 @@ export function uiRawTagEditor(context) {
function bindTypeahead(key, value) {
if (isReadOnly(key.datum())) return;
var geometry = context.geometry(_entityID);
if (typeof value.datum().value !== 'string' && _keyValues) {
value.call(uiCombobox(context, 'tag-value')
.minItems(1)
.fetcher(function(value, callback) {
var keyString = utilGetSetValue(key);
if (!_keyValues[keyString]) return;
var data = Array.from(_keyValues[keyString]).map(function(tagValue) {
return {
value: tagValue,
title: tagValue
};
});
callback(data);
}));
return;
}
var geometry = context.geometry(_entityIDs[0]);
key.call(uiCombobox(context, 'tag-key')
.fetcher(function(value, callback) {
@@ -432,6 +466,8 @@ export function uiRawTagEditor(context) {
function keyChange(d) {
if (d3_select(this).attr('readonly')) return;
var kOld = d.key;
var kNew = this.value.trim();
var row = this.parentNode.parentNode;
@@ -487,6 +523,9 @@ export function uiRawTagEditor(context) {
function valueChange(d) {
if (isReadOnly(d)) return;
// exit if this is a multiselection and no value was entered
if (typeof d.value !== 'string' && !this.value) return;
_pendingChange = _pendingChange || {};
// exit if we are currently about to delete this row anyway - #6366
@@ -550,7 +589,7 @@ export function uiRawTagEditor(context) {
rawTagEditor.preset = function(val) {
if (!arguments.length) return _preset;
_preset = val;
if (_preset.isFallback()) {
if (_preset && _preset.isFallback()) {
_expanded = true;
_updatePreference = false;
} else {
@@ -568,12 +607,72 @@ export function uiRawTagEditor(context) {
};
rawTagEditor.entityID = function(val) {
if (!arguments.length) return _entityID;
if (_entityID !== val) {
rawTagEditor.entityIDs = function(val) {
if (!arguments.length) return _entityIDs;
if (_entityIDs !== val) {
_entityIDs = val;
_orderedKeys = [];
_entityID = val;
}
if (_entityIDs.length > 1) {
// require the list editor when editing multiple entities
_tagView = 'list';
} else {
_tagView = (context.storage('raw-tag-editor-view') || 'list');
}
var combinedTags = {};
var sharedKeys = null;
_keyValues = {};
_entityIDs.forEach(function(entityID) {
var entity = context.entity(entityID);
var entityTags = entity.tags;
var entityKey;
if (sharedKeys === null) {
sharedKeys = {};
for (entityKey in entityTags) {
sharedKeys[entityKey] = true;
}
} else {
for (var sharedKey in sharedKeys) {
if (!entityTags.hasOwnProperty(sharedKey)) {
delete sharedKeys[sharedKey];
}
}
}
for (entityKey in entityTags) {
var entityValue = entityTags[entityKey];
if (!_keyValues.hasOwnProperty(entityKey)) {
_keyValues[entityKey] = new Set();
}
_keyValues[entityKey].add(entityValue);
if (combinedTags.hasOwnProperty(entityKey)) {
var combinedValue = combinedTags[entityKey];
if (combinedValue !== true &&
combinedValue !== entityValue) {
combinedTags[entityKey] = true;
}
} else {
combinedTags[entityKey] = entityValue;
}
}
});
for (var key in combinedTags) {
if (!sharedKeys.hasOwnProperty(key)) {
// treat tags that aren't shared by all entities the same as if there are multiple values
combinedTags[key] = true;
}
}
rawTagEditor.tags(combinedTags);
return rawTagEditor;
};
+13 -18
View File
@@ -1,13 +1,15 @@
import { event as d3_event, select as d3_select } from 'd3-selection';
import { t } from '../util/locale';
import { modeSelect } from '../modes/select';
import { osmEntity } from '../osm';
import { svgIcon } from '../svg/icon';
import { utilDisplayName, utilHighlightEntities } from '../util';
export function uiSelectionList(context, selectedIDs) {
export function uiSelectionList(context) {
var selectedIDs = [];
function selectEntity(entity) {
context.enter(modeSelect(context, [entity.id]));
@@ -25,24 +27,12 @@ export function uiSelectionList(context, selectedIDs) {
function selectionList(selection) {
selection.classed('selection-list-pane', true);
var header = selection
var list = selection.selectAll('.feature-list')
.data([0])
.enter()
.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');
.attr('class', 'feature-list');
context.history()
.on('change.selectionList', function(difference) {
@@ -119,5 +109,10 @@ export function uiSelectionList(context, selectedIDs) {
}
}
selectionList.setSelectedIDs = function(val) {
selectedIDs = val;
return selectionList;
};
return selectionList;
}
+17 -17
View File
@@ -167,10 +167,10 @@ export function uiSidebar(context) {
.classed('inspector-hidden', false)
.classed('inspector-hover', true);
if (inspector.entityID() !== datum.id || inspector.state() !== 'hover') {
if (inspector.entityIDs() !== [datum.id] || inspector.state() !== 'hover') {
inspector
.state('hover')
.entityID(datum.id);
.entityIDs([datum.id]);
inspectorWrap
.call(inspector);
@@ -206,17 +206,16 @@ export function uiSidebar(context) {
};
sidebar.select = function(id, newFeature) {
sidebar.select = function(ids, newFeature) {
sidebar.hide();
if (id) {
var entity = context.entity(id);
// uncollapse the sidebar
if (selection.classed('collapsed')) {
if (newFeature) {
var extent = entity.extent(context.graph());
sidebar.expand(sidebar.intersects(extent));
}
if (ids && ids.length) {
var entity = ids.length === 1 && context.entity(ids[0]);
if (entity && newFeature && selection.classed('collapsed')) {
// uncollapse the sidebar
var extent = entity.extent(context.graph());
sidebar.expand(sidebar.intersects(extent));
}
featureListWrap
@@ -226,20 +225,16 @@ export function uiSidebar(context) {
.classed('inspector-hidden', false)
.classed('inspector-hover', false);
if (inspector.entityID() !== id || inspector.state() !== 'select') {
if (inspector.entityIDs() !== ids || inspector.state() !== 'select') {
inspector
.state('select')
.entityID(id)
.entityIDs(ids)
.newFeature(newFeature);
inspectorWrap
.call(inspector, newFeature);
}
sidebar.showPresetList = function() {
inspector.showList(context.presets().match(entity, context.graph()));
};
} else {
inspector
.state('hide');
@@ -247,6 +242,11 @@ export function uiSidebar(context) {
};
sidebar.showPresetList = function() {
inspector.showList();
};
sidebar.show = function(component, element) {
featureListWrap
.classed('inspector-hidden', true);