Conflict resolution window
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
The purpose of the conflict resoltion modal is to present two versions of a conflicted item,
|
||||
and allow the user to choose which to keep (or to keep both.)
|
||||
*/
|
||||
|
||||
class ConflictResolutionModal {
|
||||
|
||||
constructor() {
|
||||
this.restrict = "E";
|
||||
this.templateUrl = "directives/conflict-resolution-modal.html";
|
||||
this.scope = {
|
||||
item1: "=",
|
||||
item2: "=",
|
||||
callback: "="
|
||||
};
|
||||
}
|
||||
|
||||
link($scope, el, attrs) {
|
||||
|
||||
$scope.dismiss = function() {
|
||||
el.remove();
|
||||
}
|
||||
}
|
||||
|
||||
controller($scope, modelManager, syncManager) {
|
||||
'ngInject';
|
||||
|
||||
$scope.createContentString = function(item) {
|
||||
return JSON.stringify(
|
||||
Object.assign({created_at: item.created_at, updated_at: item.updated_at}, item.content), null, 2
|
||||
)
|
||||
}
|
||||
|
||||
$scope.contentType = $scope.item1.content_type;
|
||||
|
||||
$scope.item1Content = $scope.createContentString($scope.item1);
|
||||
$scope.item2Content = $scope.createContentString($scope.item2);
|
||||
|
||||
$scope.keepItem1 = function() {
|
||||
if(!confirm("Are you sure you want to delete the item on the right?")) {
|
||||
return;
|
||||
}
|
||||
modelManager.setItemToBeDeleted($scope.item2);
|
||||
syncManager.sync().then(() => {
|
||||
$scope.applyCallback();
|
||||
})
|
||||
|
||||
$scope.dismiss();
|
||||
}
|
||||
|
||||
$scope.keepItem2 = function() {
|
||||
if(!confirm("Are you sure you want to delete the item on the left?")) {
|
||||
return;
|
||||
}
|
||||
modelManager.setItemToBeDeleted($scope.item1);
|
||||
syncManager.sync().then(() => {
|
||||
$scope.applyCallback();
|
||||
})
|
||||
|
||||
$scope.dismiss();
|
||||
}
|
||||
|
||||
$scope.keepBoth = function() {
|
||||
$scope.applyCallback();
|
||||
$scope.dismiss();
|
||||
}
|
||||
|
||||
$scope.applyCallback = function() {
|
||||
$scope.callback && $scope.callback();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('app').directive('conflictResolutionModal', () => new ConflictResolutionModal);
|
||||
@@ -319,6 +319,7 @@ class ComponentManager {
|
||||
install-local-component
|
||||
toggle-activate-component
|
||||
request-permissions
|
||||
present-conflict-resolution
|
||||
*/
|
||||
|
||||
if(message.action === "stream-items") {
|
||||
@@ -340,6 +341,8 @@ class ComponentManager {
|
||||
this.handleRequestPermissionsMessage(component, message);
|
||||
} else if(message.action === "install-local-component") {
|
||||
this.handleInstallLocalComponentMessage(component, message);
|
||||
} else if(message.action === "present-conflict-resolution") {
|
||||
this.handlePresentConflictResolutionMessage(component, message);
|
||||
}
|
||||
|
||||
// Notify observers
|
||||
@@ -352,6 +355,7 @@ class ComponentManager {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
removePrivatePropertiesFromResponseItems(responseItems, component, options = {}) {
|
||||
if(component) {
|
||||
// System extensions can bypass this step
|
||||
@@ -365,7 +369,6 @@ class ComponentManager {
|
||||
if(options.includeUrls) { privateProperties = privateProperties.concat(["url", "hosted_url", "local_url"])}
|
||||
}
|
||||
for(var responseItem of responseItems) {
|
||||
|
||||
// Do not pass in actual items here, otherwise that would be destructive.
|
||||
// Instead, generic JS/JSON objects should be passed.
|
||||
console.assert(typeof responseItem.setDirty !== 'function');
|
||||
@@ -376,6 +379,15 @@ class ComponentManager {
|
||||
}
|
||||
}
|
||||
|
||||
handlePresentConflictResolutionMessage(component, message) {
|
||||
console.log("handlePresentConflictResolutionMessage", message);
|
||||
var ids = message.data.item_ids;
|
||||
var items = this.modelManager.findItems(ids);
|
||||
this.syncManager.presentConflictResolutionModal(items, () => {
|
||||
this.replyToMessage(component, message, {});
|
||||
});
|
||||
}
|
||||
|
||||
handleStreamItemsMessage(component, message) {
|
||||
var requiredPermissions = [
|
||||
{
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
class SyncManager extends SFSyncManager {
|
||||
|
||||
constructor(modelManager, storageManager, httpManager, $timeout, $interval) {
|
||||
constructor(modelManager, storageManager, httpManager, $timeout, $interval, $compile, $rootScope) {
|
||||
super(modelManager, storageManager, httpManager, $timeout, $interval);
|
||||
this.$rootScope = $rootScope;
|
||||
this.$compile = $compile;
|
||||
}
|
||||
|
||||
presentConflictResolutionModal(items, callback) {
|
||||
var scope = this.$rootScope.$new(true);
|
||||
scope.item1 = items[0];
|
||||
scope.item2 = items[1];
|
||||
scope.callback = callback;
|
||||
var el = this.$compile( "<conflict-resolution-modal item1='item1' item2='item2' callback='callback' class='modal'></conflict-resolution-modal>" )(scope);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
angular.module('app').service('syncManager', SyncManager);
|
||||
|
||||
@@ -23,6 +23,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
#conflict-resolution-modal {
|
||||
#items {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.border {
|
||||
height: 100%;
|
||||
background-color: rgba(black, 0.1);
|
||||
width: 1px;
|
||||
margin: 0 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel {
|
||||
background-color: white;
|
||||
}
|
||||
@@ -61,6 +81,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
> .content {
|
||||
width: 900px;
|
||||
height: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
&.medium {
|
||||
> .content {
|
||||
width: 700px;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
.modal.large#conflict-resolution-modal
|
||||
.content
|
||||
.sn-component
|
||||
.panel
|
||||
.header
|
||||
%h1.title Conflicted items — choose which version to keep
|
||||
.horizontal-group
|
||||
%a.close-button.info{"ng-click" => "keepItem1()"} Keep left
|
||||
%a.close-button.info{"ng-click" => "keepItem2()"} Keep right
|
||||
%a.close-button.info{"ng-click" => "keepBoth()"} Keep both
|
||||
%a.close-button.info{"ng-click" => "dismiss(); $event.stopPropagation()"} Close
|
||||
.content.selectable
|
||||
.panel-section
|
||||
%h3
|
||||
%strong Content type:
|
||||
{{contentType}}
|
||||
%p You may wish to look at the "created_at" and "updated_at" fields of the items to gain better context in deciding which to keep.
|
||||
#items
|
||||
.panel.static#item1.item.border-color
|
||||
%p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{item1Content}}
|
||||
|
||||
.border
|
||||
|
||||
.panel.static#item2.item
|
||||
%p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{item2Content}}
|
||||
Reference in New Issue
Block a user