diff --git a/app/assets/javascripts/app/app.js b/app/assets/javascripts/app/app.js index d75836ddc..af4ac2d2e 100644 --- a/app/assets/javascripts/app/app.js +++ b/app/assets/javascripts/app/app.js @@ -41,6 +41,12 @@ function isMacApplication() { return window && window.process && window.process.type && window.process.platform == "darwin"; } -Array.prototype.containsSubset = function(array) { +/* Use with numbers and strings, not objects */ +Array.prototype.containsPrimitiveSubset = function(array) { return !array.some(val => this.indexOf(val) === -1); } + +/* Use with numbers and strings, not objects */ +Array.prototype.containsObjectSubset = function(array) { + return !array.some(val => !_.find(this, val)); +} diff --git a/app/assets/javascripts/app/directives/views/permissionsModal.js b/app/assets/javascripts/app/directives/views/permissionsModal.js index fb7609d55..0e1764910 100644 --- a/app/assets/javascripts/app/directives/views/permissionsModal.js +++ b/app/assets/javascripts/app/directives/views/permissionsModal.js @@ -30,46 +30,68 @@ class PermissionsModal { controller($scope, modelManager) { - $scope.formattedPermissions = $scope.permissions.map(function(permission){ - if(permission.name === "stream-items") { - var types = permission.content_types.map(function(type){ - var desc = modelManager.humanReadableDisplayForContentType(type); - if(desc) { - return desc + "s"; - } else { - return "items of type " + type; - } - }) - var typesString = ""; - var separator = ", "; + $scope.permissionsString = function() { + var finalString = ""; + let permissionsCount = $scope.permissions.length; - for(var i = 0;i < types.length;i++) { - var type = types[i]; - if(i == 0) { - // first element - typesString = typesString + type; - } else if(i == types.length - 1) { - // last element - if(types.length > 2) { - typesString += separator + "and " + type; - } else if(types.length == 2) { - typesString = typesString + " and " + type; + let addSeparator = (index, length) => { + if(index > 0) { + if(index == length - 1) { + if(length == 2) { + return " and "; + } else { + return ", and " } } else { - typesString += separator + type; + return ", "; } } - return typesString; - } else if(permission.name === "stream-context-item") { - var mapping = { - "editor-stack" : "working note", - "note-tags" : "working note", - "editor-editor": "working note" - } - return mapping[$scope.component.area]; + return ""; } - }) + + $scope.permissions.forEach((permission, index) => { + + if(permission.name === "stream-items") { + var types = permission.content_types.map(function(type){ + var desc = modelManager.humanReadableDisplayForContentType(type); + if(desc) { + return desc + "s"; + } else { + return "items of type " + type; + } + }) + var typesString = ""; + + for(var i = 0;i < types.length;i++) { + var type = types[i]; + typesString += addSeparator(i, types.length + permissionsCount - index - 1); + typesString += type; + } + + finalString += addSeparator(index, permissionsCount); + + finalString += typesString; + + if(types.length >= 2 && index < permissionsCount - 1) { + // If you have a list of types, and still an additional root-level permission coming up, add a comma + finalString += ", "; + } + } else if(permission.name === "stream-context-item") { + var mapping = { + "editor-stack" : "working note", + "note-tags" : "working note", + "editor-editor": "working note" + } + + finalString += addSeparator(index, permissionsCount, true); + + finalString += mapping[$scope.component.area]; + } + }) + + return finalString + "."; + } } } diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 627ad5119..f28c8c4f0 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -235,6 +235,7 @@ class ComponentManager { get-context-client-data install-local-component toggle-activate-component + request-permissions */ if(message.action === "stream-items") { @@ -252,6 +253,8 @@ class ComponentManager { } else if(message.action === "toggle-activate-component") { let componentToToggle = this.modelManager.findItem(message.data.uuid); this.handleToggleComponentMessage(component, componentToToggle, message); + } else if(message.action === "request-permissions") { + this.handleRequestPermissionsMessage(component, message); } // Notify observers @@ -450,6 +453,12 @@ class ComponentManager { }); } + handleRequestPermissionsMessage(component, message) { + this.runWithPermissions(component, message.data.permissions, () => { + this.replyToMessage(component, message, {approved: true}); + }); + } + handleSetComponentDataMessage(component, message) { // A component setting its own data does not require special permissions this.runWithPermissions(component, [], () => { @@ -459,7 +468,6 @@ class ComponentManager { }); } - handleToggleComponentMessage(sourceComponent, targetComponent, message) { if(targetComponent.area == "modal") { this.openModalComponent(targetComponent); @@ -504,7 +512,7 @@ class ComponentManager { matching = acquiredPermissions.find((candidate) => { return Array.isArray(candidate.content_types) && Array.isArray(required.content_types) - && candidate.content_types.containsSubset(required.content_types); + && candidate.content_types.containsPrimitiveSubset(required.content_types); }); if(!matching) { @@ -514,8 +522,6 @@ class ComponentManager { } } - // var acquiredMatchesRequested = angular.toJson(component.permissions.sort()) === angular.toJson(requestedPermissions.sort()); - if(!acquiredMatchesRequired) { this.promptForPermissions(component, requiredPermissions, function(approved){ if(approved) { @@ -528,9 +534,6 @@ class ComponentManager { } promptForPermissions(component, permissions, callback) { - // since these calls are asyncronous, multiple dialogs may be requested at the same time. We only want to present one and trigger all callbacks based on one modal result - var existingDialog = _.find(this.permissionDialogs, {component: component}); - var scope = this.$rootScope.$new(true); scope.component = component; scope.permissions = permissions; @@ -547,28 +550,47 @@ class ComponentManager { this.syncManager.sync(); } - for(var existing of this.permissionDialogs) { - if(existing.component === component && existing.actionBlock) { - existing.actionBlock(approved); + this.permissionDialogs = this.permissionDialogs.filter((pendingDialog) => { + // Remove self + if(pendingDialog == scope) { + return false; } - } - - this.permissionDialogs = this.permissionDialogs.filter(function(dialog){ - return dialog.component !== component; + if(approved && pendingDialog.component == component) { + // remove pending dialogs that are encapsulated by already approved permissions, and run its function + if(pendingDialog.permissions == permissions || permissions.containsObjectSubset(pendingDialog.permissions)) { + pendingDialog.actionBlock && pendingDialog.actionBlock(approved); + return false; + } + } + return true; }) + if(this.permissionDialogs.length > 0) { + this.presentDialog(this.permissionDialogs[0]); + } + }.bind(this); + // since these calls are asyncronous, multiple dialogs may be requested at the same time. We only want to present one and trigger all callbacks based on one modal result + var existingDialog = _.find(this.permissionDialogs, {component: component}); + this.permissionDialogs.push(scope); if(!existingDialog) { - var el = this.$compile( "" )(scope); - angular.element(document.body).append(el); + this.presentDialog(scope); } else { console.log("Existing dialog, not presenting."); } } + presentDialog(dialog) { + var permissions = dialog.permissions; + var component = dialog.component; + var callback = dialog.callback; + var el = this.$compile( "" )(dialog); + angular.element(document.body).append(el); + } + openModalComponent(component) { var scope = this.$rootScope.$new(true); scope.component = component; diff --git a/app/assets/templates/directives/permissions-modal.html.haml b/app/assets/templates/directives/permissions-modal.html.haml index d88280a3a..63a5dc38f 100644 --- a/app/assets/templates/directives/permissions-modal.html.haml +++ b/app/assets/templates/directives/permissions-modal.html.haml @@ -12,8 +12,7 @@ %h3 %strong {{component.name}} would like to interact with your - %span{"ng-repeat" => "permission in formattedPermissions"} - {{permission}}. + {{permissionsString()}} .panel-row %p