From 8569098e8a4d3dfd85df712a92ef815363266d5a Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Mon, 25 Dec 2017 15:39:00 -0600 Subject: [PATCH] Room remeber size + component permissions --- .../app/frontend/models/app/component.js | 8 + .../app/services/componentManager.js | 211 +++++++++++------- .../app/services/directives/views/roomBar.js | 25 ++- .../app/services/packageManager.js | 1 + app/assets/stylesheets/app/_footer.scss | 15 ++ .../frontend/directives/room-bar.html.haml | 3 +- config/application.rb | 2 +- 7 files changed, 175 insertions(+), 90 deletions(-) diff --git a/app/assets/javascripts/app/frontend/models/app/component.js b/app/assets/javascripts/app/frontend/models/app/component.js index d9e736ad4..eb515ce32 100644 --- a/app/assets/javascripts/app/frontend/models/app/component.js +++ b/app/assets/javascripts/app/frontend/models/app/component.js @@ -76,6 +76,14 @@ class Component extends Item { return this.getAppDataItem("defaultEditor") == true; } + setRoomLastSize(size) { + this.setAppDataItem("lastRoomSize", size); + } + + getRoomLastSize() { + return this.getAppDataItem("lastRoomSize"); + } + /* An associative component depends on being explicitly activated for a given item, compared to a dissaciative component, diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index 4e1e1c00c..3bd723017 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -225,89 +225,24 @@ class ComponentManager { if(message.action === "stream-items") { this.handleStreamItemsMessage(component, message); - } - - else if(message.action === "stream-context-item") { + } else if(message.action === "stream-context-item") { this.handleStreamContextItemMessage(component, message); - } - - else if(message.action === "set-component-data") { - component.componentData = message.data.componentData; - component.setDirty(true); - this.syncManager.sync(); - } - - else if(message.action === "delete-items") { - 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); - this.modelManager.setItemToBeDeleted(model); - } - - this.syncManager.sync(); - } - } - - else if(message.action === "create-item") { - var responseItem = message.data.item; - this.removePrivatePropertiesFromResponseItems([responseItem]); - var item = this.modelManager.createItem(responseItem); - if(responseItem.clientData) { - item.setDomainDataItem(component.url, responseItem.clientData, ClientDataDomain); - } - this.modelManager.addItem(item); - this.modelManager.resolveReferencesForItem(item); - item.setDirty(true); - this.syncManager.sync(); - this.replyToMessage(component, message, {item: this.jsonForItem(item, component)}) - } - - else if(message.action === "save-items") { - var responseItems = message.data.items; - - this.removePrivatePropertiesFromResponseItems(responseItems); - - /* - We map the items here because modelManager is what updates the UI. If you were to instead get the items directly, - this would update them server side via sync, but would never make its way back to the UI. - */ - var localItems = this.modelManager.mapResponseItemsToLocalModels(responseItems, ModelManager.MappingSourceComponentRetrieved); - - for(var item of localItems) { - var responseItem = _.find(responseItems, {uuid: item.uuid}); - _.merge(item.content, responseItem.content); - if(responseItem.clientData) { - item.setDomainDataItem(component.url, responseItem.clientData, ClientDataDomain); - } - item.setDirty(true); - } - this.syncManager.sync((response) => { - // Allow handlers to be notified when a save begins and ends, to update the UI - var saveMessage = Object.assign({}, message); - saveMessage.action = response && response.error ? "save-error" : "save-success"; - this.handleMessage(component, saveMessage); - }); - } - - else if(message.action === "install-local-component") { - console.log("Received install-local-component event"); - this.desktopManager.installOfflineComponentFromData(message.data, (response) => { - console.log("componentManager: installed component:", response); - var component = this.modelManager.mapResponseItemsToLocalModels([response], ModelManager.MappingSourceComponentRetrieved)[0]; - // Save updated URL - component.setDirty(true); - this.syncManager.sync(); - }) - } - - else if(message.action === "open-component") { + } else if(message.action === "set-component-data") { + this.handleSetComponentDataMessage(component, message); + } else if(message.action === "delete-items") { + this.handleDeleteItemsMessage(component, message); + } else if(message.action === "create-item") { + this.handleCreateItemMessage(component, message); + } else if(message.action === "save-items") { + this.handleSaveItemsMessage(component, message); + } else if(message.action === "install-local-component") { + this.handleInstallLocalComponentMessage(component, message); + } else if(message.action === "open-component") { let openComponent = this.modelManager.findItem(message.data.uuid); - console.log("Received open-component event", openComponent); this.openModalComponent(openComponent); } + // Notify observers for(let handler of this.handlers) { if(handler.areas.includes(component.area)) { this.timeout(function(){ @@ -390,6 +325,124 @@ class ComponentManager { }.bind(this)) } + handleSaveItemsMessage(component, message) { + var requiredContentTypes = _.uniq(message.data.items.map((i) => {return i.content_type})).sort(); + var requiredPermissions = [ + { + name: "stream-items", + content_types: requiredContentTypes + } + ]; + + this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + var responseItems = message.data.items; + + this.removePrivatePropertiesFromResponseItems(responseItems); + + /* + We map the items here because modelManager is what updates the UI. If you were to instead get the items directly, + this would update them server side via sync, but would never make its way back to the UI. + */ + var localItems = this.modelManager.mapResponseItemsToLocalModels(responseItems, ModelManager.MappingSourceComponentRetrieved); + + for(var item of localItems) { + var responseItem = _.find(responseItems, {uuid: item.uuid}); + _.merge(item.content, responseItem.content); + if(responseItem.clientData) { + item.setDomainDataItem(component.url, responseItem.clientData, ClientDataDomain); + } + item.setDirty(true); + } + this.syncManager.sync((response) => { + // Allow handlers to be notified when a save begins and ends, to update the UI + var saveMessage = Object.assign({}, message); + saveMessage.action = response && response.error ? "save-error" : "save-success"; + this.handleMessage(component, saveMessage); + }); + }); + } + + handleCreateItemMessage(component, message) { + var requiredPermissions = [ + { + name: "stream-items", + content_types: [message.data.item.content_type] + } + ]; + + this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + var responseItem = message.data.item; + this.removePrivatePropertiesFromResponseItems([responseItem]); + var item = this.modelManager.createItem(responseItem); + if(responseItem.clientData) { + item.setDomainDataItem(component.url, responseItem.clientData, ClientDataDomain); + } + this.modelManager.addItem(item); + this.modelManager.resolveReferencesForItem(item); + item.setDirty(true); + this.syncManager.sync(); + this.replyToMessage(component, message, {item: this.jsonForItem(item, component)}) + }); + } + + handleDeleteItemsMessage(component, message) { + var requiredContentTypes = _.uniq(message.data.items.map((i) => {return i.content_type})).sort(); + var requiredPermissions = [ + { + name: "stream-items", + content_types: requiredContentTypes + } + ]; + + this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + 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); + this.modelManager.setItemToBeDeleted(model); + } + + this.syncManager.sync(); + } + }); + } + + handleInstallLocalComponentMessage(component, message) { + var requiredPermissions = [ + { + name: "stream-items", + content_types: [message.data.content_type] + } + ]; + + this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + console.log("Received install-local-component event"); + this.desktopManager.installOfflineComponentFromData(message.data, (response) => { + console.log("componentManager: installed component:", response); + var component = this.modelManager.mapResponseItemsToLocalModels([response], ModelManager.MappingSourceComponentRetrieved)[0]; + // Save updated URL + component.setDirty(true); + this.syncManager.sync(); + }) + }); + } + + handleSetComponentDataMessage(component, message) { + var requiredPermissions = [ + { + name: "stream-items", + content_types: component.content_type + } + ]; + + this.runWithPermissions(component, requiredPermissions, message.permissions, () => { + component.componentData = message.data.componentData; + component.setDirty(true); + this.syncManager.sync(); + }); + } + runWithPermissions(component, requiredPermissions, requestedPermissions, runFunction) { var acquiredPermissions = component.permissions; @@ -407,7 +460,7 @@ class ComponentManager { if(!requestedMatchesRequired) { // Error with Component permissions request console.error("You are requesting permissions", requestedPermissions, "when you need to be requesting", requiredPermissions, ". Component:", component); - return; + return false; } if(!component.permissions) { diff --git a/app/assets/javascripts/app/services/directives/views/roomBar.js b/app/assets/javascripts/app/services/directives/views/roomBar.js index 4d9c552cb..b03eab93c 100644 --- a/app/assets/javascripts/app/services/directives/views/roomBar.js +++ b/app/assets/javascripts/app/services/directives/views/roomBar.js @@ -11,29 +11,36 @@ class RoomBar { 'ngInject'; $scope.componentManager = componentManager; + $scope.rooms = []; - $rootScope.$on("initial-data-loaded", () => { - $timeout(() => { - $scope.rooms = componentManager.componentsForArea("rooms"); - }) + modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => { + $scope.rooms = _.uniq($scope.rooms + .concat(allItems + .filter((candidate) => {return candidate.area == "rooms"}))) + .filter((candidate) => {return !candidate.deleted}); }); - componentManager.registerHandler({identifier: "roomBar", areas: ["rooms"], activationHandler: function(component){ + componentManager.registerHandler({identifier: "roomBar", areas: ["rooms"], activationHandler: (component) => { if(component.active) { - $timeout(function(){ + $timeout(() => { var iframe = componentManager.iframeForComponent(component); if(iframe) { + var lastSize = component.getRoomLastSize(); + if(lastSize) { + componentManager.handleSetSizeEvent(component, lastSize); + } iframe.onload = function() { componentManager.registerComponentWindow(component, iframe.contentWindow); }.bind(this); } - }.bind(this)); + }); } - }.bind(this), actionHandler: function(component, action, data){ + }, actionHandler: (component, action, data) => { if(action == "set-size") { componentManager.handleSetSizeEvent(component, data); + component.setRoomLastSize(data); } - }.bind(this)}); + }}); $scope.selectRoom = function(room) { room.show = !room.show; diff --git a/app/assets/javascripts/app/services/packageManager.js b/app/assets/javascripts/app/services/packageManager.js index d3a16666f..048d5b2ae 100644 --- a/app/assets/javascripts/app/services/packageManager.js +++ b/app/assets/javascripts/app/services/packageManager.js @@ -16,6 +16,7 @@ class PackageManager { } var assembled = this.modelManager.createItem(aPackage); + assembled.package_info = aPackage; this.modelManager.addItem(assembled); assembled.setDirty(true); this.syncManager.sync(); diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index 7dc364919..acd0f887a 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -95,6 +95,21 @@ h2 { .room-item { display: inline-block; position: relative; + vertical-align: middle; + + .label { + display: inline-block; + vertical-align: middle; + font-size: 11px; + margin-top: -2px; + } + + .icon { + width: 16px; + height: 16px; + display: inline-block; + vertical-align: middle; + } .room-container { max-height: 85vh; diff --git a/app/assets/templates/frontend/directives/room-bar.html.haml b/app/assets/templates/frontend/directives/room-bar.html.haml index ab4c78ffa..08cb5059b 100644 --- a/app/assets/templates/frontend/directives/room-bar.html.haml +++ b/app/assets/templates/frontend/directives/room-bar.html.haml @@ -1,4 +1,5 @@ .room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)"} - %span {{room.name}} + %img.icon{"ng-src" => "{{room.package_info.icon_bar}}"} + .label {{room.name}} .room-container.panel-right{"ng-if" => "room.show && room.active", "ng-attr-id" => "component-{{room.uuid}}"} %iframe.room-iframe{"ng-src" => "{{componentManager.urlForComponent(room) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{room.uuid}}"} diff --git a/config/application.rb b/config/application.rb index 16e4e7e40..ac93fc14b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -53,7 +53,7 @@ module Neeto font_src: %w(* 'self'), form_action: %w('self'), frame_ancestors: ["*"], - img_src: %w('self' data:), + img_src: %w('self' * data:), manifest_src: %w('self'), media_src: %w('self'), object_src: %w('self'),