mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 01:02:58 +00:00
Merge pull request #7333 from openstreetmap/uploader
Add upload controller
This commit is contained in:
@@ -9,6 +9,7 @@ import { t, currentLocale, addTranslation, setLocale } from '../util/locale';
|
||||
import { coreData } from './data';
|
||||
import { coreHistory } from './history';
|
||||
import { coreValidator } from './validator';
|
||||
import { coreUploader } from './uploader';
|
||||
import { dataLocales, dataEn } from '../../data';
|
||||
import { geoRawMercator } from '../geo/raw_mercator';
|
||||
import { modeSelect } from '../modes/select';
|
||||
@@ -96,10 +97,12 @@ export function coreContext() {
|
||||
let _data;
|
||||
let _history;
|
||||
let _validator;
|
||||
let _uploader;
|
||||
context.connection = () => _connection;
|
||||
context.data = () => _data;
|
||||
context.history = () => _history;
|
||||
context.validator = () => _validator;
|
||||
context.uploader = () => _uploader;
|
||||
|
||||
/* Connection */
|
||||
context.preauth = (options) => {
|
||||
@@ -466,6 +469,10 @@ export function coreContext() {
|
||||
_validator.reset();
|
||||
_features.reset();
|
||||
_history.reset();
|
||||
_uploader.reset();
|
||||
|
||||
// don't leave stale state in the inspector
|
||||
d3_select('.inspector-wrap *').remove();
|
||||
|
||||
return context;
|
||||
};
|
||||
@@ -484,6 +491,7 @@ export function coreContext() {
|
||||
_data = coreData(context);
|
||||
_history = coreHistory(context);
|
||||
_validator = coreValidator(context);
|
||||
_uploader = coreUploader(context);
|
||||
|
||||
context.graph = _history.graph;
|
||||
context.changes = _history.changes;
|
||||
|
||||
@@ -4,4 +4,5 @@ export { coreDifference } from './difference';
|
||||
export { coreGraph } from './graph';
|
||||
export { coreHistory } from './history';
|
||||
export { coreTree } from './tree';
|
||||
export { coreUploader } from './uploader';
|
||||
export { coreValidator } from './validator';
|
||||
|
||||
381
modules/core/uploader.js
Normal file
381
modules/core/uploader.js
Normal file
@@ -0,0 +1,381 @@
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
|
||||
import { actionDiscardTags } from '../actions/discard_tags';
|
||||
import { actionMergeRemoteChanges } from '../actions/merge_remote_changes';
|
||||
import { actionNoop } from '../actions/noop';
|
||||
import { actionRevert } from '../actions/revert';
|
||||
import { coreGraph } from '../core/graph';
|
||||
import { t } from '../util/locale';
|
||||
import { utilArrayUnion, utilArrayUniq, utilDisplayName, utilDisplayType, utilRebind } from '../util';
|
||||
|
||||
|
||||
export function coreUploader(context) {
|
||||
|
||||
var dispatch = d3_dispatch(
|
||||
// Start and end events are dispatched exactly once each per legitimate outside call to `save`
|
||||
'saveStarted', // dispatched as soon as a call to `save` has been deemed legitimate
|
||||
'saveEnded', // dispatched after the result event has been dispatched
|
||||
|
||||
'willAttemptUpload', // dispatched before the actual upload call occurs, if it will
|
||||
'progressChanged',
|
||||
|
||||
// Each save results in one of these outcomes:
|
||||
'resultNoChanges', // upload wasn't attempted since there were no edits
|
||||
'resultErrors', // upload failed due to errors
|
||||
'resultConflicts', // upload failed due to data conflicts
|
||||
'resultSuccess' // upload completed without errors
|
||||
);
|
||||
|
||||
var _isSaving = false;
|
||||
|
||||
var _toCheck = [];
|
||||
var _toLoad = [];
|
||||
var _loaded = {};
|
||||
var _toLoadCount = 0;
|
||||
var _toLoadTotal = 0;
|
||||
|
||||
var _conflicts = [];
|
||||
var _errors = [];
|
||||
var _origChanges;
|
||||
|
||||
var _discardTags = {};
|
||||
context.data().get('discarded')
|
||||
.then(function(d) { _discardTags = d; })
|
||||
.catch(function() { /* ignore */ });
|
||||
|
||||
var uploader = utilRebind({}, dispatch, 'on');
|
||||
|
||||
uploader.isSaving = function() {
|
||||
return _isSaving;
|
||||
};
|
||||
|
||||
uploader.save = function(changeset, tryAgain, checkConflicts) {
|
||||
// Guard against accidentally entering save code twice - #4641
|
||||
if (_isSaving && !tryAgain) {
|
||||
return;
|
||||
}
|
||||
|
||||
var osm = context.connection();
|
||||
if (!osm) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If user somehow got logged out mid-save, try to reauthenticate..
|
||||
// This can happen if they were logged in from before, but the tokens are no longer valid.
|
||||
if (!osm.authenticated()) {
|
||||
osm.authenticate(function(err) {
|
||||
if (!err) {
|
||||
uploader.save(changeset, tryAgain, checkConflicts); // continue where we left off..
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isSaving) {
|
||||
_isSaving = true;
|
||||
dispatch.call('saveStarted', this);
|
||||
}
|
||||
|
||||
var history = context.history();
|
||||
var localGraph = context.graph();
|
||||
var remoteGraph = coreGraph(history.base(), true);
|
||||
|
||||
_conflicts = [];
|
||||
_errors = [];
|
||||
|
||||
// Store original changes, in case user wants to download them as an .osc file
|
||||
_origChanges = history.changes(actionDiscardTags(history.difference(), _discardTags));
|
||||
|
||||
// First time, `history.perform` a no-op action.
|
||||
// Any conflict resolutions will be done as `history.replace`
|
||||
if (!tryAgain) {
|
||||
history.perform(actionNoop());
|
||||
}
|
||||
|
||||
// Attempt a fast upload.. If there are conflicts, re-enter with `checkConflicts = true`
|
||||
if (!checkConflicts) {
|
||||
upload(changeset);
|
||||
|
||||
// Do the full (slow) conflict check..
|
||||
} else {
|
||||
var summary = history.difference().summary();
|
||||
_toCheck = [];
|
||||
for (var i = 0; i < summary.length; i++) {
|
||||
var item = summary[i];
|
||||
if (item.changeType === 'modified') {
|
||||
_toCheck.push(item.entity.id);
|
||||
}
|
||||
}
|
||||
|
||||
_toLoad = withChildNodes(_toCheck, localGraph);
|
||||
_loaded = {};
|
||||
_toLoadCount = 0;
|
||||
_toLoadTotal = _toLoad.length;
|
||||
|
||||
if (_toCheck.length) {
|
||||
dispatch.call('progressChanged', this, _toLoadCount, _toLoadTotal);
|
||||
_toLoad.forEach(function(id) { _loaded[id] = false; });
|
||||
osm.loadMultiple(_toLoad, loaded);
|
||||
} else {
|
||||
upload(changeset);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
|
||||
function withChildNodes(ids, graph) {
|
||||
var s = new Set(ids);
|
||||
ids.forEach(function(id) {
|
||||
var entity = graph.entity(id);
|
||||
if (entity.type !== 'way') return;
|
||||
|
||||
graph.childNodes(entity).forEach(function(child) {
|
||||
if (child.version !== undefined) {
|
||||
s.add(child.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(s);
|
||||
}
|
||||
|
||||
|
||||
// Reload modified entities into an alternate graph and check for conflicts..
|
||||
function loaded(err, result) {
|
||||
if (_errors.length) return;
|
||||
|
||||
if (err) {
|
||||
_errors.push({
|
||||
msg: err.message || err.responseText,
|
||||
details: [ t('save.status_code', { code: err.status }) ]
|
||||
});
|
||||
hasErrors();
|
||||
|
||||
} else {
|
||||
var loadMore = [];
|
||||
|
||||
result.data.forEach(function(entity) {
|
||||
remoteGraph.replace(entity);
|
||||
_loaded[entity.id] = true;
|
||||
_toLoad = _toLoad.filter(function(val) { return val !== entity.id; });
|
||||
|
||||
if (!entity.visible) return;
|
||||
|
||||
// Because loadMultiple doesn't download /full like loadEntity,
|
||||
// need to also load children that aren't already being checked..
|
||||
var i, id;
|
||||
if (entity.type === 'way') {
|
||||
for (i = 0; i < entity.nodes.length; i++) {
|
||||
id = entity.nodes[i];
|
||||
if (_loaded[id] === undefined) {
|
||||
_loaded[id] = false;
|
||||
loadMore.push(id);
|
||||
}
|
||||
}
|
||||
} else if (entity.type === 'relation' && entity.isMultipolygon()) {
|
||||
for (i = 0; i < entity.members.length; i++) {
|
||||
id = entity.members[i].id;
|
||||
if (_loaded[id] === undefined) {
|
||||
_loaded[id] = false;
|
||||
loadMore.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_toLoadCount += result.data.length;
|
||||
_toLoadTotal += loadMore.length;
|
||||
dispatch.call('progressChanged', this, _toLoadCount, _toLoadTotal);
|
||||
|
||||
if (loadMore.length) {
|
||||
_toLoad.push.apply(_toLoad, loadMore);
|
||||
osm.loadMultiple(loadMore, loaded);
|
||||
}
|
||||
|
||||
if (!_toLoad.length) {
|
||||
detectConflicts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function detectConflicts() {
|
||||
function choice(id, text, action) {
|
||||
return {
|
||||
id: id,
|
||||
text: text,
|
||||
action: function() {
|
||||
history.replace(action);
|
||||
}
|
||||
};
|
||||
}
|
||||
function formatUser(d) {
|
||||
return '<a href="' + osm.userURL(d) + '" target="_blank">' + d + '</a>';
|
||||
}
|
||||
function entityName(entity) {
|
||||
return utilDisplayName(entity) || (utilDisplayType(entity.id) + ' ' + entity.id);
|
||||
}
|
||||
|
||||
function sameVersions(local, remote) {
|
||||
if (local.version !== remote.version) return false;
|
||||
|
||||
if (local.type === 'way') {
|
||||
var children = utilArrayUnion(local.nodes, remote.nodes);
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var a = localGraph.hasEntity(children[i]);
|
||||
var b = remoteGraph.hasEntity(children[i]);
|
||||
if (a && b && a.version !== b.version) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_toCheck.forEach(function(id) {
|
||||
var local = localGraph.entity(id);
|
||||
var remote = remoteGraph.entity(id);
|
||||
|
||||
if (sameVersions(local, remote)) return;
|
||||
|
||||
var merge = actionMergeRemoteChanges(id, localGraph, remoteGraph, _discardTags, formatUser);
|
||||
|
||||
history.replace(merge);
|
||||
|
||||
var mergeConflicts = merge.conflicts();
|
||||
if (!mergeConflicts.length) return; // merged safely
|
||||
|
||||
var forceLocal = actionMergeRemoteChanges(id, localGraph, remoteGraph, _discardTags).withOption('force_local');
|
||||
var forceRemote = actionMergeRemoteChanges(id, localGraph, remoteGraph, _discardTags).withOption('force_remote');
|
||||
var keepMine = t('save.conflict.' + (remote.visible ? 'keep_local' : 'restore'));
|
||||
var keepTheirs = t('save.conflict.' + (remote.visible ? 'keep_remote' : 'delete'));
|
||||
|
||||
_conflicts.push({
|
||||
id: id,
|
||||
name: entityName(local),
|
||||
details: mergeConflicts,
|
||||
chosen: 1,
|
||||
choices: [
|
||||
choice(id, keepMine, forceLocal),
|
||||
choice(id, keepTheirs, forceRemote)
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
upload(changeset);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function upload(changeset) {
|
||||
var osm = context.connection();
|
||||
if (!osm) {
|
||||
_errors.push({ msg: 'No OSM Service' });
|
||||
}
|
||||
|
||||
if (_conflicts.length) {
|
||||
|
||||
_conflicts.sort(function(a, b) { return b.id.localeCompare(a.id); });
|
||||
|
||||
dispatch.call('resultConflicts', this, changeset, _conflicts, _origChanges);
|
||||
|
||||
endSave();
|
||||
|
||||
} else if (_errors.length) {
|
||||
hasErrors();
|
||||
|
||||
} else {
|
||||
var history = context.history();
|
||||
var changes = history.changes(actionDiscardTags(history.difference(), _discardTags));
|
||||
if (changes.modified.length || changes.created.length || changes.deleted.length) {
|
||||
// fire off some async work that we want to be ready later
|
||||
dispatch.call('willAttemptUpload', this);
|
||||
osm.putChangeset(changeset, changes, uploadCallback);
|
||||
} else { // changes were insignificant or reverted by user
|
||||
|
||||
dispatch.call('resultNoChanges', this);
|
||||
|
||||
endSave();
|
||||
|
||||
// reset iD
|
||||
context.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function uploadCallback(err, changeset) {
|
||||
if (err) {
|
||||
if (err.status === 409) { // 409 Conflict
|
||||
uploader.save(changeset, true, true); // tryAgain = true, checkConflicts = true
|
||||
} else {
|
||||
_errors.push({
|
||||
msg: err.message || err.responseText,
|
||||
details: [ t('save.status_code', { code: err.status }) ]
|
||||
});
|
||||
hasErrors();
|
||||
}
|
||||
|
||||
} else {
|
||||
context.history().clearSaved();
|
||||
dispatch.call('resultSuccess', this, changeset);
|
||||
// Add delay to allow for postgres replication #1646 #2678
|
||||
window.setTimeout(function() {
|
||||
endSave();
|
||||
|
||||
// reset iD
|
||||
context.flush();
|
||||
}, 2500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function hasErrors() {
|
||||
|
||||
context.history().pop();
|
||||
|
||||
dispatch.call('resultErrors', this, _errors);
|
||||
|
||||
endSave();
|
||||
}
|
||||
|
||||
|
||||
function endSave() {
|
||||
_isSaving = false;
|
||||
|
||||
dispatch.call('saveEnded', this);
|
||||
}
|
||||
|
||||
|
||||
uploader.cancelConflictResolution = function() {
|
||||
context.history().pop();
|
||||
};
|
||||
|
||||
|
||||
uploader.processResolvedConflicts = function(changeset) {
|
||||
var history = context.history();
|
||||
|
||||
for (var i = 0; i < _conflicts.length; i++) {
|
||||
if (_conflicts[i].chosen === 1) { // user chose "keep theirs"
|
||||
var entity = context.hasEntity(_conflicts[i].id);
|
||||
if (entity && entity.type === 'way') {
|
||||
var children = utilArrayUniq(entity.nodes);
|
||||
for (var j = 0; j < children.length; j++) {
|
||||
history.replace(actionRevert(children[j]));
|
||||
}
|
||||
}
|
||||
history.replace(actionRevert(_conflicts[i].id));
|
||||
}
|
||||
}
|
||||
|
||||
uploader.save(changeset, true, false); // tryAgain = true, checkConflicts = false
|
||||
};
|
||||
|
||||
|
||||
uploader.reset = function() {
|
||||
|
||||
};
|
||||
|
||||
|
||||
return uploader;
|
||||
}
|
||||
@@ -1,53 +1,38 @@
|
||||
import { event as d3_event, select as d3_select } from 'd3-selection';
|
||||
import { t } from '../util/locale';
|
||||
|
||||
import { actionDiscardTags } from '../actions/discard_tags';
|
||||
import { actionMergeRemoteChanges } from '../actions/merge_remote_changes';
|
||||
import { actionNoop } from '../actions/noop';
|
||||
import { actionRevert } from '../actions/revert';
|
||||
import { coreGraph } from '../core/graph';
|
||||
import { modeBrowse } from './browse';
|
||||
import { modeSelect } from './select';
|
||||
import { services } from '../services';
|
||||
import { uiConflicts } from '../ui/conflicts';
|
||||
import { uiConfirm } from '../ui/confirm';
|
||||
import { uiCommit } from '../ui/commit';
|
||||
import { uiLoading } from '../ui/loading';
|
||||
import { uiSuccess } from '../ui/success';
|
||||
import { utilArrayUnion, utilArrayUniq, utilDisplayName, utilDisplayType, utilKeybinding } from '../util';
|
||||
|
||||
|
||||
var _isSaving = false;
|
||||
import { utilKeybinding } from '../util';
|
||||
|
||||
|
||||
export function modeSave(context) {
|
||||
var mode = { id: 'save' };
|
||||
var keybinding = utilKeybinding('modeSave');
|
||||
|
||||
var loading = uiLoading(context)
|
||||
.message(t('save.uploading'))
|
||||
.blocking(true);
|
||||
|
||||
var commit = uiCommit(context)
|
||||
.on('cancel', cancel)
|
||||
.on('save', save);
|
||||
.on('cancel', cancel);
|
||||
|
||||
var _toCheck = [];
|
||||
var _toLoad = [];
|
||||
var _loaded = {};
|
||||
var _toLoadCount = 0;
|
||||
var _toLoadTotal = 0;
|
||||
|
||||
var _conflicts = [];
|
||||
var _errors = [];
|
||||
var _origChanges;
|
||||
var _location;
|
||||
var _success;
|
||||
|
||||
var _discardTags = {};
|
||||
context.data().get('discarded')
|
||||
.then(function(d) { _discardTags = d; })
|
||||
.catch(function() { /* ignore */ });
|
||||
var uploader = context.uploader()
|
||||
.on('saveStarted.modeSave', function() {
|
||||
keybindingOff();
|
||||
})
|
||||
.on('willAttemptUpload.modeSave', prepareForSuccess)
|
||||
.on('progressChanged.modeSave', showProgress)
|
||||
.on('resultNoChanges.modeSave', function() {
|
||||
cancel();
|
||||
})
|
||||
.on('resultErrors.modeSave', showErrors)
|
||||
.on('resultConflicts.modeSave', showConflicts)
|
||||
.on('resultSuccess.modeSave', showSuccess);
|
||||
|
||||
|
||||
function cancel(selectedID) {
|
||||
@@ -59,280 +44,6 @@ export function modeSave(context) {
|
||||
}
|
||||
|
||||
|
||||
function save(changeset, tryAgain, checkConflicts) {
|
||||
// Guard against accidentally entering save code twice - #4641
|
||||
if (_isSaving && !tryAgain) {
|
||||
return;
|
||||
}
|
||||
|
||||
var osm = context.connection();
|
||||
if (!osm) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// If user somehow got logged out mid-save, try to reauthenticate..
|
||||
// This can happen if they were logged in from before, but the tokens are no longer valid.
|
||||
if (!osm.authenticated()) {
|
||||
osm.authenticate(function(err) {
|
||||
if (err) {
|
||||
cancel(); // quit save mode..
|
||||
} else {
|
||||
save(changeset, tryAgain, checkConflicts); // continue where we left off..
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isSaving) {
|
||||
keybindingOff();
|
||||
context.container().call(loading); // block input
|
||||
_isSaving = true;
|
||||
}
|
||||
|
||||
var history = context.history();
|
||||
var localGraph = context.graph();
|
||||
var remoteGraph = coreGraph(history.base(), true);
|
||||
|
||||
_conflicts = [];
|
||||
_errors = [];
|
||||
|
||||
// Store original changes, in case user wants to download them as an .osc file
|
||||
_origChanges = history.changes(actionDiscardTags(history.difference(), _discardTags));
|
||||
|
||||
// First time, `history.perform` a no-op action.
|
||||
// Any conflict resolutions will be done as `history.replace`
|
||||
if (!tryAgain) {
|
||||
history.perform(actionNoop());
|
||||
}
|
||||
|
||||
// Attempt a fast upload.. If there are conflicts, re-enter with `checkConflicts = true`
|
||||
if (!checkConflicts) {
|
||||
upload(changeset);
|
||||
|
||||
// Do the full (slow) conflict check..
|
||||
} else {
|
||||
var summary = history.difference().summary();
|
||||
_toCheck = [];
|
||||
for (var i = 0; i < summary.length; i++) {
|
||||
var item = summary[i];
|
||||
if (item.changeType === 'modified') {
|
||||
_toCheck.push(item.entity.id);
|
||||
}
|
||||
}
|
||||
|
||||
_toLoad = withChildNodes(_toCheck, localGraph);
|
||||
_loaded = {};
|
||||
_toLoadCount = 0;
|
||||
_toLoadTotal = _toLoad.length;
|
||||
|
||||
if (_toCheck.length) {
|
||||
showProgress(_toLoadCount, _toLoadTotal);
|
||||
_toLoad.forEach(function(id) { _loaded[id] = false; });
|
||||
osm.loadMultiple(_toLoad, loaded);
|
||||
} else {
|
||||
upload(changeset);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
|
||||
function withChildNodes(ids, graph) {
|
||||
var s = new Set(ids);
|
||||
ids.forEach(function(id) {
|
||||
var entity = graph.entity(id);
|
||||
if (entity.type !== 'way') return;
|
||||
|
||||
graph.childNodes(entity).forEach(function(child) {
|
||||
if (child.version !== undefined) {
|
||||
s.add(child.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(s);
|
||||
}
|
||||
|
||||
|
||||
// Reload modified entities into an alternate graph and check for conflicts..
|
||||
function loaded(err, result) {
|
||||
if (_errors.length) return;
|
||||
|
||||
if (err) {
|
||||
_errors.push({
|
||||
msg: err.message || err.responseText,
|
||||
details: [ t('save.status_code', { code: err.status }) ]
|
||||
});
|
||||
showErrors();
|
||||
|
||||
} else {
|
||||
var loadMore = [];
|
||||
|
||||
result.data.forEach(function(entity) {
|
||||
remoteGraph.replace(entity);
|
||||
_loaded[entity.id] = true;
|
||||
_toLoad = _toLoad.filter(function(val) { return val !== entity.id; });
|
||||
|
||||
if (!entity.visible) return;
|
||||
|
||||
// Because loadMultiple doesn't download /full like loadEntity,
|
||||
// need to also load children that aren't already being checked..
|
||||
var i, id;
|
||||
if (entity.type === 'way') {
|
||||
for (i = 0; i < entity.nodes.length; i++) {
|
||||
id = entity.nodes[i];
|
||||
if (_loaded[id] === undefined) {
|
||||
_loaded[id] = false;
|
||||
loadMore.push(id);
|
||||
}
|
||||
}
|
||||
} else if (entity.type === 'relation' && entity.isMultipolygon()) {
|
||||
for (i = 0; i < entity.members.length; i++) {
|
||||
id = entity.members[i].id;
|
||||
if (_loaded[id] === undefined) {
|
||||
_loaded[id] = false;
|
||||
loadMore.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_toLoadCount += result.data.length;
|
||||
_toLoadTotal += loadMore.length;
|
||||
showProgress(_toLoadCount, _toLoadTotal);
|
||||
|
||||
if (loadMore.length) {
|
||||
_toLoad.push.apply(_toLoad, loadMore);
|
||||
osm.loadMultiple(loadMore, loaded);
|
||||
}
|
||||
|
||||
if (!_toLoad.length) {
|
||||
detectConflicts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function detectConflicts() {
|
||||
function choice(id, text, action) {
|
||||
return { id: id, text: text, action: function() { history.replace(action); } };
|
||||
}
|
||||
function formatUser(d) {
|
||||
return '<a href="' + osm.userURL(d) + '" target="_blank">' + d + '</a>';
|
||||
}
|
||||
function entityName(entity) {
|
||||
return utilDisplayName(entity) || (utilDisplayType(entity.id) + ' ' + entity.id);
|
||||
}
|
||||
|
||||
function sameVersions(local, remote) {
|
||||
if (local.version !== remote.version) return false;
|
||||
|
||||
if (local.type === 'way') {
|
||||
var children = utilArrayUnion(local.nodes, remote.nodes);
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var a = localGraph.hasEntity(children[i]);
|
||||
var b = remoteGraph.hasEntity(children[i]);
|
||||
if (a && b && a.version !== b.version) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_toCheck.forEach(function(id) {
|
||||
var local = localGraph.entity(id);
|
||||
var remote = remoteGraph.entity(id);
|
||||
|
||||
if (sameVersions(local, remote)) return;
|
||||
|
||||
var action = actionMergeRemoteChanges;
|
||||
var merge = action(id, localGraph, remoteGraph, _discardTags, formatUser);
|
||||
|
||||
history.replace(merge);
|
||||
|
||||
var mergeConflicts = merge.conflicts();
|
||||
if (!mergeConflicts.length) return; // merged safely
|
||||
|
||||
var forceLocal = action(id, localGraph, remoteGraph, _discardTags).withOption('force_local');
|
||||
var forceRemote = action(id, localGraph, remoteGraph, _discardTags).withOption('force_remote');
|
||||
var keepMine = t('save.conflict.' + (remote.visible ? 'keep_local' : 'restore'));
|
||||
var keepTheirs = t('save.conflict.' + (remote.visible ? 'keep_remote' : 'delete'));
|
||||
|
||||
_conflicts.push({
|
||||
id: id,
|
||||
name: entityName(local),
|
||||
details: mergeConflicts,
|
||||
chosen: 1,
|
||||
choices: [
|
||||
choice(id, keepMine, forceLocal),
|
||||
choice(id, keepTheirs, forceRemote)
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
upload(changeset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function upload(changeset) {
|
||||
var osm = context.connection();
|
||||
if (!osm) {
|
||||
_errors.push({ msg: 'No OSM Service' });
|
||||
}
|
||||
|
||||
if (_conflicts.length) {
|
||||
_conflicts.sort(function(a, b) { return b.id.localeCompare(a.id); });
|
||||
showConflicts(changeset);
|
||||
|
||||
} else if (_errors.length) {
|
||||
showErrors();
|
||||
|
||||
} else {
|
||||
var history = context.history();
|
||||
var changes = history.changes(actionDiscardTags(history.difference(), _discardTags));
|
||||
if (changes.modified.length || changes.created.length || changes.deleted.length) {
|
||||
// fire off some async work that we want to be ready later
|
||||
prepareForSuccess(); // geocode current location and load community index
|
||||
osm.putChangeset(changeset, changes, uploadCallback);
|
||||
} else { // changes were insignificant or reverted by user
|
||||
d3_select('.inspector-wrap *').remove();
|
||||
loading.close();
|
||||
_isSaving = false;
|
||||
context.flush();
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function uploadCallback(err, changeset) {
|
||||
if (err) {
|
||||
if (err.status === 409) { // 409 Conflict
|
||||
save(changeset, true, true); // tryAgain = true, checkConflicts = true
|
||||
} else {
|
||||
_errors.push({
|
||||
msg: err.message || err.responseText,
|
||||
details: [ t('save.status_code', { code: err.status }) ]
|
||||
});
|
||||
showErrors();
|
||||
}
|
||||
|
||||
} else {
|
||||
context.history().clearSaved();
|
||||
showSuccess(changeset);
|
||||
// Add delay to allow for postgres replication #1646 #2678
|
||||
window.setTimeout(function() {
|
||||
d3_select('.inspector-wrap *').remove();
|
||||
loading.close();
|
||||
_isSaving = false;
|
||||
context.flush();
|
||||
}, 2500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function showProgress(num, total) {
|
||||
var modal = context.container().select('.loading-modal .modal-section');
|
||||
var progress = modal.selectAll('.progress')
|
||||
@@ -347,60 +58,41 @@ export function modeSave(context) {
|
||||
}
|
||||
|
||||
|
||||
function showConflicts(changeset) {
|
||||
var history = context.history();
|
||||
function showConflicts(changeset, conflicts, origChanges) {
|
||||
|
||||
var selection = context.container()
|
||||
.select('#sidebar')
|
||||
.append('div')
|
||||
.attr('class','sidebar-component');
|
||||
|
||||
loading.close();
|
||||
_isSaving = false;
|
||||
|
||||
context.container().selectAll('#content')
|
||||
.attr('class', 'active');
|
||||
|
||||
var ui = uiConflicts(context)
|
||||
.conflictList(_conflicts)
|
||||
.origChanges(_origChanges)
|
||||
.conflictList(conflicts)
|
||||
.origChanges(origChanges)
|
||||
.on('cancel', function() {
|
||||
context.container().selectAll('#content')
|
||||
.attr('class', 'inactive');
|
||||
|
||||
history.pop();
|
||||
.attr('class', 'inactive')
|
||||
selection.remove();
|
||||
keybindingOn();
|
||||
|
||||
uploader.cancelConflictResolution();
|
||||
})
|
||||
.on('save', function() {
|
||||
context.container().selectAll('#content')
|
||||
.attr('class', 'inactive');
|
||||
|
||||
for (var i = 0; i < _conflicts.length; i++) {
|
||||
if (_conflicts[i].chosen === 1) { // user chose "keep theirs"
|
||||
var entity = context.hasEntity(_conflicts[i].id);
|
||||
if (entity && entity.type === 'way') {
|
||||
var children = utilArrayUniq(entity.nodes);
|
||||
for (var j = 0; j < children.length; j++) {
|
||||
history.replace(actionRevert(children[j]));
|
||||
}
|
||||
}
|
||||
history.replace(actionRevert(_conflicts[i].id));
|
||||
}
|
||||
}
|
||||
|
||||
selection.remove();
|
||||
save(changeset, true, false); // tryAgain = true, checkConflicts = false
|
||||
|
||||
uploader.processResolvedConflicts(changeset);
|
||||
});
|
||||
|
||||
selection.call(ui);
|
||||
}
|
||||
|
||||
|
||||
function showErrors() {
|
||||
function showErrors(errors) {
|
||||
keybindingOn();
|
||||
context.history().pop();
|
||||
loading.close();
|
||||
_isSaving = false;
|
||||
|
||||
var selection = uiConfirm(context.container());
|
||||
selection
|
||||
@@ -408,7 +100,7 @@ export function modeSave(context) {
|
||||
.append('h3')
|
||||
.text(t('save.error'));
|
||||
|
||||
addErrors(selection, _errors);
|
||||
addErrors(selection, errors);
|
||||
selection.okButton();
|
||||
}
|
||||
|
||||
@@ -542,7 +234,6 @@ export function modeSave(context) {
|
||||
|
||||
|
||||
mode.exit = function() {
|
||||
_isSaving = false;
|
||||
|
||||
keybindingOff();
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ var hashtagRegex = /(#[^\u2000-\u206F\u2E00-\u2E7F\s\\'!"#$%()*,.\/:;<=>?@\[\]^`
|
||||
|
||||
|
||||
export function uiCommit(context) {
|
||||
var dispatch = d3_dispatch('cancel', 'save');
|
||||
var dispatch = d3_dispatch('cancel');
|
||||
var _userDetails;
|
||||
var _selection;
|
||||
|
||||
@@ -372,7 +372,7 @@ export function uiCommit(context) {
|
||||
.on('click.save', function() {
|
||||
if (!d3_select(this).classed('disabled')) {
|
||||
this.blur(); // avoid keeping focus on the button - #4641
|
||||
dispatch.call('save', this, _changeset);
|
||||
context.uploader().save(_changeset);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -484,5 +484,20 @@ export function uiInit(context) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var _saveLoading = d3_select(null);
|
||||
|
||||
context.uploader()
|
||||
.on('saveStarted.ui', function() {
|
||||
_saveLoading = uiLoading(context)
|
||||
.message(t('save.uploading'))
|
||||
.blocking(true);
|
||||
context.container().call(_saveLoading); // block input during upload
|
||||
})
|
||||
.on('saveEnded.ui', function() {
|
||||
_saveLoading.close();
|
||||
_saveLoading = d3_select(null);
|
||||
});
|
||||
|
||||
return ui;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user