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
|
install-local-component
|
||||||
toggle-activate-component
|
toggle-activate-component
|
||||||
request-permissions
|
request-permissions
|
||||||
|
present-conflict-resolution
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if(message.action === "stream-items") {
|
if(message.action === "stream-items") {
|
||||||
@@ -340,6 +341,8 @@ class ComponentManager {
|
|||||||
this.handleRequestPermissionsMessage(component, message);
|
this.handleRequestPermissionsMessage(component, message);
|
||||||
} else if(message.action === "install-local-component") {
|
} else if(message.action === "install-local-component") {
|
||||||
this.handleInstallLocalComponentMessage(component, message);
|
this.handleInstallLocalComponentMessage(component, message);
|
||||||
|
} else if(message.action === "present-conflict-resolution") {
|
||||||
|
this.handlePresentConflictResolutionMessage(component, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify observers
|
// Notify observers
|
||||||
@@ -352,6 +355,7 @@ class ComponentManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
removePrivatePropertiesFromResponseItems(responseItems, component, options = {}) {
|
removePrivatePropertiesFromResponseItems(responseItems, component, options = {}) {
|
||||||
if(component) {
|
if(component) {
|
||||||
// System extensions can bypass this step
|
// System extensions can bypass this step
|
||||||
@@ -365,7 +369,6 @@ class ComponentManager {
|
|||||||
if(options.includeUrls) { privateProperties = privateProperties.concat(["url", "hosted_url", "local_url"])}
|
if(options.includeUrls) { privateProperties = privateProperties.concat(["url", "hosted_url", "local_url"])}
|
||||||
}
|
}
|
||||||
for(var responseItem of responseItems) {
|
for(var responseItem of responseItems) {
|
||||||
|
|
||||||
// Do not pass in actual items here, otherwise that would be destructive.
|
// Do not pass in actual items here, otherwise that would be destructive.
|
||||||
// Instead, generic JS/JSON objects should be passed.
|
// Instead, generic JS/JSON objects should be passed.
|
||||||
console.assert(typeof responseItem.setDirty !== 'function');
|
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) {
|
handleStreamItemsMessage(component, message) {
|
||||||
var requiredPermissions = [
|
var requiredPermissions = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
class SyncManager extends SFSyncManager {
|
class SyncManager extends SFSyncManager {
|
||||||
|
|
||||||
constructor(modelManager, storageManager, httpManager, $timeout, $interval) {
|
constructor(modelManager, storageManager, httpManager, $timeout, $interval, $compile, $rootScope) {
|
||||||
super(modelManager, storageManager, httpManager, $timeout, $interval);
|
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);
|
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 {
|
.panel {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
@@ -61,6 +81,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.large {
|
||||||
|
> .content {
|
||||||
|
width: 900px;
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.medium {
|
&.medium {
|
||||||
> .content {
|
> .content {
|
||||||
width: 700px;
|
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