diff --git a/app/assets/javascripts/app/controllers/editor.js b/app/assets/javascripts/app/controllers/editor.js index 70e2e6675..448c99d96 100644 --- a/app/assets/javascripts/app/controllers/editor.js +++ b/app/assets/javascripts/app/controllers/editor.js @@ -497,14 +497,18 @@ angular.module('app') } this.toggleStackComponentForCurrentItem = function(component) { - if(component.active) { + if(component.hidden) { + // Unhide, associate with current item + component.hidden = false; + if(!component.active) { + componentManager.activateComponent(component); + } + this.associateComponentWithCurrentNote(component); + componentManager.contextItemDidChangeInArea("editor-stack"); + } else { + // not hidden, hide component.hidden = true; this.disassociateComponentWithCurrentNote(component); - } else { - // Inactive - componentManager.activateComponent(component); - componentManager.contextItemDidChangeInArea("editor-stack"); - this.associateComponentWithCurrentNote(component); } } diff --git a/app/assets/javascripts/app/models/api/item.js b/app/assets/javascripts/app/models/api/item.js index b05f94adb..7671b061e 100644 --- a/app/assets/javascripts/app/models/api/item.js +++ b/app/assets/javascripts/app/models/api/item.js @@ -50,9 +50,17 @@ class Item { if(json.content) { this.mapContentToLocalProperties(this.contentObject); + } else if(json.deleted == true) { + this.handleDeletedContent(); } } + + /* Allows the item to handle the case where the item is deleted and the content is null */ + handleDeletedContent() { + // Subclasses can override + } + setDirty(dirty) { this.dirty = dirty; diff --git a/app/assets/javascripts/app/models/app/component.js b/app/assets/javascripts/app/models/app/component.js index a94ff9748..18ca71daa 100644 --- a/app/assets/javascripts/app/models/app/component.js +++ b/app/assets/javascripts/app/models/app/component.js @@ -38,6 +38,10 @@ class Component extends Item { this.area = content.area; this.permissions = content.permissions; + if(!this.permissions) { + this.permissions = []; + } + this.active = content.active; // custom data that a component can store in itself @@ -50,6 +54,12 @@ class Component extends Item { this.associatedItemIds = content.associatedItemIds || []; } + handleDeletedContent() { + super.handleDeletedContent(); + + this.active = false; + } + structureParams() { var params = { url: this.url, diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 55072029e..d15e8b816 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -193,7 +193,8 @@ class ComponentManager { } sendMessageToComponent(component, message) { - if(component.hidden && message.action !== "component-registered") { + let permissibleActionsWhileHidden = ["component-registered", "themes"]; + if(component.hidden && !permissibleActionsWhileHidden.includes(message.action)) { if(this.loggingEnabled) { console.log("Component disabled for current item, not sending any messages.", component.name); } @@ -471,11 +472,15 @@ class ComponentManager { ]; this.runWithPermissions(component, requiredPermissions, () => { - var items = message.data.items; - var noun = items.length == 1 ? "item" : "items"; - if(confirm(`Are you sure you want to delete ${items.length} ${noun}?`)) { - for(var item of items) { - var model = this.modelManager.findItem(item.uuid); + var itemsData = message.data.items; + var noun = itemsData.length == 1 ? "item" : "items"; + if(confirm(`Are you sure you want to delete ${itemsData.length} ${noun}?`)) { + // Filter for any components and deactivate before deleting + for(var itemData of itemsData) { + var model = this.modelManager.findItem(itemData.uuid); + if(["SN|Component", "SN|Theme"].includes(model.content_type)) { + this.deactivateComponent(model, true); + } this.modelManager.setItemToBeDeleted(model); } @@ -598,10 +603,14 @@ class ComponentManager { return false; } - if(approved && pendingDialog.component == component) { + if(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); + // If approved, run the action block. Otherwise, if canceled, cancel any pending ones as well, since the user was + // explicit in their intentions + if(approved) { + pendingDialog.actionBlock && pendingDialog.actionBlock(approved); + } return false; } } @@ -641,30 +650,6 @@ class ComponentManager { angular.element(document.body).append(el); } - activateComponent(component) { - var didChange = component.active != true; - - component.active = true; - for(var handler of this.handlers) { - if(handler.areas.includes(component.area) || handler.areas.includes("*")) { - handler.activationHandler(component); - } - } - - if(didChange) { - component.setDirty(true); - this.syncManager.sync("activateComponent"); - } - - if(!this.activeComponents.includes(component)) { - this.activeComponents.push(component); - } - - if(component.area == "themes") { - this.postThemeToAllComponents(); - } - } - registerHandler(handler) { this.handlers.push(handler); } @@ -699,7 +684,31 @@ class ComponentManager { this.postThemeToComponent(component); } - deactivateComponent(component) { + activateComponent(component, dontSync = false) { + var didChange = component.active != true; + + component.active = true; + for(var handler of this.handlers) { + if(handler.areas.includes(component.area) || handler.areas.includes("*")) { + handler.activationHandler(component); + } + } + + if(didChange && !dontSync) { + component.setDirty(true); + this.syncManager.sync("activateComponent"); + } + + if(!this.activeComponents.includes(component)) { + this.activeComponents.push(component); + } + + if(component.area == "themes") { + this.postThemeToAllComponents(); + } + } + + deactivateComponent(component, dontSync = false) { var didChange = component.active != false; component.active = false; component.sessionKey = null; @@ -710,7 +719,7 @@ class ComponentManager { } } - if(didChange) { + if(didChange && !dontSync) { component.setDirty(true); this.syncManager.sync("deactivateComponent"); } diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 916d3a968..3d858bca4 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -51,12 +51,18 @@ class DesktopManager { desktop_onComponentInstallationComplete(componentData, error) { console.log("Web|Component Installation/Update Complete", componentData, error); + + // Desktop is only allowed to change these keys: + let permissableKeys = ["package_info", "local_url"]; var component = this.modelManager.findItem(componentData.uuid); + if(error) { - component = this.modelManager.findItem(componentData.uuid); component.setAppDataItem("installError", error); } else { - component = this.modelManager.mapResponseItemsToLocalModels([componentData], ModelManager.MappingSourceDesktopInstalled)[0]; + for(var key of permissableKeys) { + component[key] = componentData.content[key]; + } + this.modelManager.notifySyncObserversOfModels([component], ModelManager.MappingSourceDesktopInstalled); component.setAppDataItem("installError", null); } component.setDirty(true); diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index c1982308d..c1e274e6c 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -119,8 +119,6 @@ class ModelManager { mapResponseItemsToLocalModelsOmittingFields(items, omitFields, source) { var models = [], processedObjects = [], modelsToNotifyObserversOf = []; - // console.log("mapResponseItemsToLocalModelsOmittingFields"); - // first loop should add and process items for (var json_obj of items) { if((!json_obj.content_type || !json_obj.content) && !json_obj.deleted && !json_obj.errorDecrypting) { @@ -176,6 +174,7 @@ class ModelManager { return models; } + /* Note that this function is public, and can also be called manually (desktopManager uses it) */ notifySyncObserversOfModels(models, source) { for(var observer of this.itemSyncObservers) { var allRelevantItems = models.filter(function(item){return item.content_type == observer.type || observer.type == "*"}); diff --git a/app/assets/javascripts/app/services/syncManager.js b/app/assets/javascripts/app/services/syncManager.js index dfb8dc12b..6f3643c44 100644 --- a/app/assets/javascripts/app/services/syncManager.js +++ b/app/assets/javascripts/app/services/syncManager.js @@ -291,6 +291,7 @@ class SyncManager { response.retrieved_items = response.retrieved_items.filter((candidate) => {return !allSavedUUIDs.includes(candidate.uuid)}); // Map retrieved items to local data + // Note that deleted items will not be returned var retrieved = this.handleItemsResponse(response.retrieved_items, null, ModelManager.MappingSourceRemoteRetrieved); diff --git a/app/assets/stylesheets/app/_editor.scss b/app/assets/stylesheets/app/_editor.scss index cca66e8ee..9080ee9c4 100644 --- a/app/assets/stylesheets/app/_editor.scss +++ b/app/assets/stylesheets/app/_editor.scss @@ -117,9 +117,9 @@ $heading-height: 75px; width: 100%; .component-stack-item { - // height: 50px; width: 100%; position: relative; + &:not(:last-child) { border-bottom: 1px solid $bg-color; } diff --git a/app/assets/templates/editor.html.haml b/app/assets/templates/editor.html.haml index a32c17a72..0c4c5630d 100644 --- a/app/assets/templates/editor.html.haml +++ b/app/assets/templates/editor.html.haml @@ -53,4 +53,4 @@ %p.medium-padding{"style" => "padding-top: 0 !important;"} There was an error decrypting this item. Ensure you are running the latest version of this app, then sign out and sign back in to try again. #editor-pane-component-stack - %component-view.component-view.component-stack-item{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "ng-show" => "!component.hidden", "manual-dealloc" => "true", "component" => "component"} + %component-view.component-view.component-stack-item.border-color{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "ng-show" => "!component.hidden", "manual-dealloc" => "true", "component" => "component"} diff --git a/app/assets/templates/tags.html.haml b/app/assets/templates/tags.html.haml index ddb92a254..f99e9bfba 100644 --- a/app/assets/templates/tags.html.haml +++ b/app/assets/templates/tags.html.haml @@ -1,6 +1,8 @@ .section.tags#tags-column + .component-view-container{"ng-if" => "ctrl.component.active"} %component-view.component-view{"component" => "ctrl.component"} + #tags-content.content{"ng-if" => "!(ctrl.component && ctrl.component.active)"} #tags-title-bar.section-title-bar .section-title-bar-header