mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 09:12:52 +00:00
321 lines
9.5 KiB
JavaScript
321 lines
9.5 KiB
JavaScript
import _clone from 'lodash-es/clone';
|
|
import _forEach from 'lodash-es/forEach';
|
|
import _isEmpty from 'lodash-es/isEmpty';
|
|
import _isEqual from 'lodash-es/isEqual';
|
|
import _some from 'lodash-es/some';
|
|
|
|
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
|
|
|
import {
|
|
event as d3_event,
|
|
selectAll as d3_selectAll
|
|
} from 'd3-selection';
|
|
|
|
import { t, textDirection } from '../util/locale';
|
|
import { tooltip } from '../util/tooltip';
|
|
import { actionChangeTags } from '../actions';
|
|
import { modeBrowse } from '../modes';
|
|
import { svgIcon } from '../svg';
|
|
import { uiPresetIcon } from './preset_icon';
|
|
import { uiRawMemberEditor } from './raw_member_editor';
|
|
import { uiRawMembershipEditor } from './raw_membership_editor';
|
|
import { uiRawTagEditor } from './raw_tag_editor';
|
|
import { uiTagReference } from './tag_reference';
|
|
import { uiPresetEditor } from './preset_editor';
|
|
import { utilRebind } from '../util';
|
|
|
|
|
|
export function uiEntityEditor(context) {
|
|
var dispatch = d3_dispatch('choose'),
|
|
state = 'select',
|
|
coalesceChanges = false,
|
|
modified = false,
|
|
base,
|
|
entityId,
|
|
activePreset,
|
|
reference;
|
|
|
|
var presetEditor = uiPresetEditor(context)
|
|
.on('change', changeTags);
|
|
var rawTagEditor = uiRawTagEditor(context)
|
|
.on('change', changeTags);
|
|
|
|
|
|
function entityEditor(selection) {
|
|
var entity = context.entity(entityId),
|
|
tags = _clone(entity.tags);
|
|
|
|
// Header
|
|
var header = selection.selectAll('.header')
|
|
.data([0]);
|
|
|
|
// Enter
|
|
var enter = header.enter()
|
|
.append('div')
|
|
.attr('class', 'header fillL cf');
|
|
|
|
enter
|
|
.append('button')
|
|
.attr('class', 'fl preset-reset preset-choose')
|
|
.call(svgIcon((textDirection === 'rtl') ? '#icon-forward' : '#icon-backward'));
|
|
|
|
enter
|
|
.append('button')
|
|
.attr('class', 'fr preset-close')
|
|
.on('click', function() { context.enter(modeBrowse(context)); })
|
|
.call(svgIcon(modified ? '#icon-apply' : '#icon-close'));
|
|
|
|
enter
|
|
.append('h3')
|
|
.text(t('inspector.edit'));
|
|
|
|
// Update
|
|
header = header
|
|
.merge(enter);
|
|
|
|
header.selectAll('.preset-reset')
|
|
.on('click', function() {
|
|
dispatch.call('choose', this, activePreset);
|
|
});
|
|
|
|
|
|
// Body
|
|
var body = selection.selectAll('.inspector-body')
|
|
.data([0]);
|
|
|
|
// Enter
|
|
enter = body.enter()
|
|
.append('div')
|
|
.attr('class', 'inspector-body');
|
|
|
|
enter
|
|
.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');
|
|
|
|
enter
|
|
.append('div')
|
|
.attr('class', 'inspector-border preset-editor');
|
|
|
|
enter
|
|
.append('div')
|
|
.attr('class', 'inspector-border raw-tag-editor inspector-inner');
|
|
|
|
enter
|
|
.append('div')
|
|
.attr('class', 'inspector-border raw-member-editor inspector-inner');
|
|
|
|
enter
|
|
.append('div')
|
|
.attr('class', 'raw-membership-editor inspector-inner');
|
|
|
|
enter
|
|
.append('input')
|
|
.attr('type', 'text')
|
|
.attr('class', 'key-trap');
|
|
|
|
|
|
// Update
|
|
body = body
|
|
.merge(enter);
|
|
|
|
body.selectAll('.preset-list-button-wrap')
|
|
.call(reference.button);
|
|
|
|
body.selectAll('.preset-list-item')
|
|
.call(reference.body);
|
|
|
|
body.selectAll('.preset-reset')
|
|
.on('click', function() {
|
|
dispatch.call('choose', this, activePreset);
|
|
});
|
|
|
|
body.select('.preset-list-item button')
|
|
.call(uiPresetIcon()
|
|
.geometry(context.geometry(entityId))
|
|
.preset(activePreset)
|
|
);
|
|
|
|
body.select('.preset-list-item .label')
|
|
.text(activePreset.name());
|
|
|
|
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(uiRawMemberEditor(context)
|
|
.entityID(entityId)
|
|
);
|
|
} else {
|
|
body.select('.raw-member-editor')
|
|
.style('display', 'none');
|
|
}
|
|
|
|
body.select('.raw-membership-editor')
|
|
.call(uiRawMembershipEditor(context)
|
|
.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();
|
|
}
|
|
});
|
|
|
|
context.history()
|
|
.on('change.entity-editor', historyChanged);
|
|
|
|
|
|
function historyChanged() {
|
|
if (state === 'hide') return;
|
|
|
|
var entity = context.hasEntity(entityId);
|
|
var graph = context.graph();
|
|
if (!entity) return;
|
|
|
|
var match = context.presets().match(entity, graph);
|
|
var activePreset = entityEditor.preset();
|
|
var weakPreset = activePreset && _isEmpty(activePreset.addTags);
|
|
|
|
// 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);
|
|
}
|
|
entityEditor.modified(base !== graph);
|
|
entityEditor(selection);
|
|
}
|
|
}
|
|
|
|
|
|
function clean(o) {
|
|
|
|
function cleanVal(k, v) {
|
|
function keepSpaces(k) {
|
|
return k.match(/_hours|_times/) !== null;
|
|
}
|
|
|
|
var blacklist = ['description', 'note', 'fixme'];
|
|
if (_some(blacklist, function(s) { return k.indexOf(s) !== -1; })) return v;
|
|
|
|
var cleaned = v.split(';')
|
|
.map(function(s) { return s.trim(); })
|
|
.join(keepSpaces(k) ? '; ' : ';');
|
|
|
|
// The code below is not intended to validate websites and emails.
|
|
// It is only intended to prevent obvious copy-paste errors. (#2323)
|
|
// clean website- and email-like tags
|
|
if (k.indexOf('website') !== -1 ||
|
|
k.indexOf('email') !== -1 ||
|
|
cleaned.indexOf('http') === 0) {
|
|
cleaned = cleaned
|
|
.replace(/[\u200B-\u200F\uFEFF]/g, ''); // strip LRM and other zero width chars
|
|
|
|
}
|
|
|
|
return cleaned;
|
|
}
|
|
|
|
var out = {}, k, v;
|
|
for (k in o) {
|
|
if (k && (v = o[k]) !== undefined) {
|
|
out[k] = cleanVal(k, v);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
|
|
// 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),
|
|
annotation = t('operations.change_tags.annotation'),
|
|
tags = _clone(entity.tags);
|
|
|
|
_forEach(changed, function(v, k) {
|
|
if (v !== undefined || tags.hasOwnProperty(k)) {
|
|
tags[k] = v;
|
|
}
|
|
});
|
|
|
|
if (!onInput) {
|
|
tags = clean(tags);
|
|
}
|
|
|
|
if (!_isEqual(entity.tags, tags)) {
|
|
if (coalesceChanges) {
|
|
context.overwrite(actionChangeTags(entityId, tags), annotation);
|
|
} else {
|
|
context.perform(actionChangeTags(entityId, tags), annotation);
|
|
coalesceChanges = !!onInput;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
entityEditor.modified = function(_) {
|
|
if (!arguments.length) return modified;
|
|
modified = _;
|
|
d3_selectAll('button.preset-close use')
|
|
.attr('xlink:href', (modified ? '#icon-apply' : '#icon-close'));
|
|
};
|
|
|
|
|
|
entityEditor.state = function(_) {
|
|
if (!arguments.length) return state;
|
|
state = _;
|
|
return entityEditor;
|
|
};
|
|
|
|
|
|
entityEditor.entityID = function(_) {
|
|
if (!arguments.length) return entityId;
|
|
entityId = _;
|
|
base = context.graph();
|
|
entityEditor.preset(context.presets().match(context.entity(entityId), base));
|
|
entityEditor.modified(false);
|
|
coalesceChanges = false;
|
|
return entityEditor;
|
|
};
|
|
|
|
|
|
entityEditor.preset = function(_) {
|
|
if (!arguments.length) return activePreset;
|
|
if (_ !== activePreset) {
|
|
activePreset = _;
|
|
reference = uiTagReference(activePreset.reference(context.geometry(entityId)), context)
|
|
.showing(false);
|
|
}
|
|
return entityEditor;
|
|
};
|
|
|
|
|
|
return utilRebind(entityEditor, dispatch, 'on');
|
|
}
|