WIP: Add choices ui for resolving conflicts

This commit is contained in:
Bryan Housel
2015-01-11 23:13:31 -05:00
parent c420650ac8
commit fb0d17e713
3 changed files with 91 additions and 34 deletions
+10 -5
View File
@@ -312,18 +312,25 @@ en:
title: Save
help: "Save changes to OpenStreetMap, making them visible to other users."
no_changes: No changes to save.
errors: Errors occurred while trying to save
error: Errors occurred while trying to save
status_code: "Server returned status code {code}"
status_gone: '{type} "{id}" {name} has already been deleted.'
unknown_error_details: "Please ensure you are connected to the internet."
uploading: Uploading changes to OpenStreetMap.
unsaved_changes: You have unsaved changes
conflicts:
conflict:
header: Conflicting edits detected
message: 'Conflicting edits were made to {type} "{id}" {name}'
keep_local: Keep Mine
keep_remote: Keep Theirs
restore: Restore
leave_deleted: Leave Deleted
delete: Leave Deleted
annotation:
safe: Merged remote changes from server.
keep_local: 'Kept local version of "{id}".'
keep_remote: 'Kept remote version of "{id}".'
restore: 'Restored local version of "{id}".'
delete: 'Deleted local version of "{id}".'
try_again: Try Again
download_changes: Download Changes
help: |
@@ -331,9 +338,7 @@ en:
You can click on each item below for more details about the conflict, and choose whether to keep
your changes or the other user's changes. Or, you can download your changes to a file.
merge_remote_changes:
annotation: Merged remote changes from server.
conflict:
general: 'Conflicting edits were made to {type} "{id}" {name}'
location: Location was changed both locally and remotely.
nodelist: Nodes were changed both locally and remotely.
memberlist: Relation members were changed both locally and remotely.
+11 -5
View File
@@ -386,27 +386,33 @@
"title": "Save",
"help": "Save changes to OpenStreetMap, making them visible to other users.",
"no_changes": "No changes to save.",
"errors": "Errors occurred while trying to save",
"error": "Errors occurred while trying to save",
"status_code": "Server returned status code {code}",
"status_gone": "{type} \"{id}\" {name} has already been deleted.",
"unknown_error_details": "Please ensure you are connected to the internet.",
"uploading": "Uploading changes to OpenStreetMap.",
"unsaved_changes": "You have unsaved changes",
"conflicts": {
"conflict": {
"header": "Conflicting edits detected",
"message": "Conflicting edits were made to {type} \"{id}\" {name}",
"keep_local": "Keep Mine",
"keep_remote": "Keep Theirs",
"restore": "Restore",
"leave_deleted": "Leave Deleted",
"delete": "Leave Deleted",
"annotation": {
"safe": "Merged remote changes from server.",
"keep_local": "Kept local version of \"{id}\".",
"keep_remote": "Kept remote version of \"{id}\".",
"restore": "Restored local version of \"{id}\".",
"delete": "Deleted local version of \"{id}\"."
},
"try_again": "Try Again",
"download_changes": "Download Changes",
"help": "It looks like another OpenStreetMap user has changed some of the same map features that you changed.\nYou can click on each item below for more details about the conflict, and choose whether to keep\nyour changes or the other user's changes. Or, you can download your changes to a file.\n"
}
},
"merge_remote_changes": {
"annotation": "Merged remote changes from server.",
"conflict": {
"general": "Conflicting edits were made to {type} \"{id}\" {name}",
"location": "Location was changed both locally and remotely.",
"nodelist": "Nodes were changed both locally and remotely.",
"memberlist": "Relation members were changed both locally and remotely.",
+70 -24
View File
@@ -3,6 +3,13 @@ iD.modes.Save = function(context) {
.on('cancel', cancel)
.on('save', save);
function choice(text, actions) {
return {
text: text,
action: function() { context.perform.apply(this, actions); }
};
}
function cancel() {
context.enter(iD.modes.Browse(context));
}
@@ -14,7 +21,8 @@ iD.modes.Save = function(context) {
toCheck = _.pluck(history.changes().modified, 'id'),
didMerge = false,
conflicts = [],
errors = [];
errors = [],
confirm;
context.container()
.call(loading);
@@ -39,14 +47,20 @@ iD.modes.Save = function(context) {
if (err.status === 410) { // Status: Gone (contains no responseText)
conflicts.push({
id: id,
msg: t('save.status_gone', {id: id, type: type, name: name}),
details: [ t('save.status_code', {code: err.status}) ]
msg: t('save.status_gone', { id: id, type: type, name: name }),
details: [ t('save.status_code', { code: err.status }) ],
choices: [
choice(t('save.conflict.restore'),
[ iD.actions.Noop() /*FIXME*/, t('save.conflict.annotation.restore', {id: id}) ]),
choice(t('save.conflict.delete'),
[ iD.actions.DeleteMultiple([id]), t('save.conflict.annotation.delete', {id: id}) ])
]
});
} else {
errors.push({
id: id,
msg: err.responseText,
details: [ t('save.status_code', {code: err.status}) ]
details: [ t('save.status_code', { code: err.status }) ]
});
}
@@ -55,16 +69,27 @@ iD.modes.Save = function(context) {
var remote = altGraph.entity(id);
if (local.version !== remote.version) {
var action = iD.actions.MergeRemoteChanges(id, graph, altGraph),
diff = history.perform(action);
var merge = iD.actions.MergeRemoteChanges,
safe = merge(id, graph, altGraph),
diff = context.perform(safe),
details = safe.conflicts();
if (diff.length()) {
didMerge = true;
} else {
var forceLocal = merge(id, graph, altGraph).withOption('force_local'),
forceRemote = merge(id, graph, altGraph).withOption('force_remote');
conflicts.push({
id: id,
msg: t('merge_remote_changes.conflict.general', {id: id, type: type, name: name}),
details: action.conflicts()
msg: t('save.conflict.message', { id: id, type: type, name: name }),
details: details,
choices: [
choice(t('save.conflict.keep_local'),
[ forceLocal, t('save.conflict.annotation.keep_local', {id: id}) ]),
choice(t('save.conflict.keep_remote'),
[ forceRemote, t('save.conflict.annotation.keep_remote', {id: id}) ])
]
});
}
}
@@ -78,7 +103,7 @@ iD.modes.Save = function(context) {
function finalize() {
if (didMerge) { // set undo checkpoint..
history.perform([iD.actions.Noop, t('merge_remote_changes.annotation')]);
context.perform(iD.actions.Noop(), t('save.conflict.annotation.safe'));
}
if (conflicts.length) {
@@ -107,20 +132,19 @@ iD.modes.Save = function(context) {
}
function showConflicts() {
var confirm = iD.ui.confirm(context.container());
confirm = iD.ui.confirm(context.container());
loading.close();
confirm
.select('.modal-section.header')
.append('h3')
.text(t('save.conflicts.header'));
.text(t('save.conflict.header'));
confirm
.select('.modal-section.message-text')
.append('div')
.attr('class', 'conflicts-help')
.text(t('save.conflicts.help'));
.text(t('save.conflict.help'));
addItems(confirm, conflicts);
@@ -135,7 +159,7 @@ iD.modes.Save = function(context) {
confirm.remove();
save(e);
})
.text(t('save.conflicts.try_again'));
.text(t('save.conflict.try_again'));
buttons
.append('button')
@@ -152,22 +176,22 @@ iD.modes.Save = function(context) {
var diff = iD.actions.DiscardTags(history.difference()),
changes = history.changes(diff),
data = JXON.stringify(context.connection().osmChangeJXON('CHANGEME', changes)),
win = window.open("data:text/xml," + encodeURIComponent(data), "_blank");
win = window.open('data:text/xml,' + encodeURIComponent(data), '_blank');
win.focus();
confirm.remove();
})
.text(t('save.conflicts.download_changes'));
.text(t('save.conflict.download_changes'));
}
function showErrors() {
var confirm = iD.ui.confirm(context.container());
confirm = iD.ui.confirm(context.container());
loading.close();
confirm
.select('.modal-section.header')
.append('h3')
.text(t('save.errors'));
.text(t('save.error'));
addItems(confirm, errors);
confirm.okButton();
@@ -193,25 +217,47 @@ iD.modes.Save = function(context) {
.text(function(d) { return d.msg || t('save.unknown_error_details'); })
.on('click', function() {
var error = d3.select(this),
details = d3.select(this.nextElementSibling),
detail = d3.select(this.nextElementSibling),
exp = error.classed('expanded');
details.style('display', exp ? 'none' : 'block');
detail.style('display', exp ? 'none' : 'block');
error.classed('expanded', !exp);
d3.event.preventDefault();
});
enter
var details = enter
.append('div')
.attr('class', 'error-detail-container')
.style('display', 'none');
details
.append('ul')
.attr('class', 'error-detail-list')
.style('display', 'none')
.selectAll('li')
.data(function(d) { return d.details; })
.data(function(d) { return d.details || []; })
.enter()
.append('li')
.attr('class', 'error-detail-item')
.text(function(d) { return d;});
.text(function(d) { return d; });
details
.append('div')
.attr('class', 'error-choices')
.selectAll('a')
.data(function(d) { return d.choices || []; })
.enter()
.append('a')
.attr('class', 'error-choice')
.text(function(d) { return d.text; })
.on('click', function(d) {
d.action();
d3.event.preventDefault();
d3.select(this.parentElement.parentElement.parentElement)
.transition()
.style('opacity', 0)
.remove();
});
items.exit()
.remove();