From 0c31668625991008cce48bcb1df3937ed845fc6f Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Sat, 23 Dec 2017 11:25:08 -0600 Subject: [PATCH] Wip --- .../app/frontend/controllers/tags.js | 2 + .../app/frontend/models/app/component.js | 5 ++ .../app/frontend/models/app/theme.js | 2 + .../app/services/componentManager.js | 53 ++++++++++++++----- .../app/services/desktopManager.js | 43 +++++++++++++++ .../services/directives/views/editorMenu.js | 6 +++ .../app/services/directives/views/roomBar.js | 45 ++++++++++++++-- app/assets/stylesheets/app/_footer.scss | 23 ++++++++ app/assets/stylesheets/app/_menus.scss | 7 ++- app/assets/stylesheets/app/_ui.scss | 16 +++--- .../frontend/directives/editor-menu.html.haml | 4 +- .../frontend/directives/room-bar.html.haml | 4 +- .../templates/frontend/editor.html.haml | 6 +-- app/assets/templates/frontend/tags.html.haml | 2 +- 14 files changed, 186 insertions(+), 32 deletions(-) diff --git a/app/assets/javascripts/app/frontend/controllers/tags.js b/app/assets/javascripts/app/frontend/controllers/tags.js index abad7e59f..e7f686b8e 100644 --- a/app/assets/javascripts/app/frontend/controllers/tags.js +++ b/app/assets/javascripts/app/frontend/controllers/tags.js @@ -38,6 +38,8 @@ angular.module('app.frontend') var initialLoad = true; + this.componentManager = componentManager; + componentManager.registerHandler({identifier: "tags", areas: ["tags-list"], activationHandler: function(component){ this.component = component; diff --git a/app/assets/javascripts/app/frontend/models/app/component.js b/app/assets/javascripts/app/frontend/models/app/component.js index ac0c30d0e..202073279 100644 --- a/app/assets/javascripts/app/frontend/models/app/component.js +++ b/app/assets/javascripts/app/frontend/models/app/component.js @@ -21,11 +21,14 @@ class Component extends Item { this.url = content.url; this.name = content.name; + this.package_info = content.package_info; + // the location in the view this component is located in. Valid values are currently tags-list, note-tags, and editor-stack` this.area = content.area; this.permissions = content.permissions; this.active = content.active; + this.local = content.local; // custom data that a component can store in itself this.componentData = content.componentData || {}; @@ -42,8 +45,10 @@ class Component extends Item { url: this.url, name: this.name, area: this.area, + package_info: this.package_info, permissions: this.permissions, active: this.active, + local: this.local, componentData: this.componentData, disassociatedItemIds: this.disassociatedItemIds, associatedItemIds: this.associatedItemIds, diff --git a/app/assets/javascripts/app/frontend/models/app/theme.js b/app/assets/javascripts/app/frontend/models/app/theme.js index ec7486012..d69eaad76 100644 --- a/app/assets/javascripts/app/frontend/models/app/theme.js +++ b/app/assets/javascripts/app/frontend/models/app/theme.js @@ -8,12 +8,14 @@ class Theme extends Item { super.mapContentToLocalProperties(content) this.url = content.url; this.name = content.name; + this.local = content.local; } structureParams() { var params = { url: this.url, name: this.name, + local: this.local }; _.merge(params, super.structureParams()); diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index c22cdbd85..049e05c75 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -3,12 +3,13 @@ let ClientDataDomain = "org.standardnotes.sn.components"; class ComponentManager { - constructor($rootScope, modelManager, syncManager, themeManager, $timeout, $compile) { + constructor($rootScope, modelManager, syncManager, desktopManager, themeManager, $timeout, $compile) { this.$compile = $compile; this.$rootScope = $rootScope; this.modelManager = modelManager; this.syncManager = syncManager; this.themeManager = themeManager; + this.desktopManager = desktopManager; this.timeout = $timeout; this.streamObservers = []; this.contextStreamObservers = []; @@ -42,6 +43,10 @@ class ComponentManager { } var syncedComponents = allItems.filter(function(item){return item.content_type === "SN|Component" }); + + // Ensure any component in our data is installed by the system + this.desktopManager.syncComponentsInstallation(syncedComponents); + for(var component of syncedComponents) { var activeComponent = _.find(this.activeComponents, {uuid: component.uuid}); if(component.active && !component.deleted && !activeComponent) { @@ -191,19 +196,20 @@ class ComponentManager { /** Possible Messages: - set-size - stream-items - stream-context-item - save-items - select-item - associate-item - deassociate-item - clear-selection - create-item - delete-items - set-component-data - save-context-client-data - get-context-client-data + set-size + stream-items + stream-context-item + save-items + select-item + associate-item + deassociate-item + clear-selection + create-item + delete-items + set-component-data + save-context-client-data + get-context-client-data + install-local-component */ if(message.action === "stream-items") { @@ -274,6 +280,17 @@ class ComponentManager { }); } + 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(); + }) + } + for(let handler of this.handlers) { if(handler.areas.includes(component.area)) { this.timeout(function(){ @@ -585,6 +602,14 @@ class ComponentManager { component.ignoreEvents = !on; } + urlForComponent(component) { + if(isDesktopApplication() && component.local && component.url.startsWith("sn://")) { + return component.url.replace("sn://", this.desktopManager.getApplicationDataPath() + "/"); + } else { + return component.url; + } + } + iframeForComponent(component) { for(var frame of document.getElementsByTagName("iframe")) { var componentId = frame.dataset.componentId; diff --git a/app/assets/javascripts/app/services/desktopManager.js b/app/assets/javascripts/app/services/desktopManager.js index 1cb28506c..ac3b3b380 100644 --- a/app/assets/javascripts/app/services/desktopManager.js +++ b/app/assets/javascripts/app/services/desktopManager.js @@ -8,6 +8,8 @@ class DesktopManager { this.authManager = authManager; this.$rootScope = $rootScope; + this.isDesktop = isDesktopApplication(); + $rootScope.$on("initial-data-loaded", () => { this.dataLoaded = true; if(this.dataLoadHandler) { @@ -22,6 +24,47 @@ class DesktopManager { }) } + /* Can handle components and themes */ + installOfflineComponentFromData(componentData, callback) { + this.componentInstallationHandler(componentData, (installedComponent) => { + componentData.content.url = installedComponent.content.url; + componentData.content.local = true; + callback(componentData); + }); + } + + getApplicationDataPath() { + console.assert(this.applicationDataPath, "applicationDataPath is null"); + return this.applicationDataPath; + } + + /* Sending a component in its raw state is really slow for the desktop app */ + convertComponentForTransmission(component) { + return new ItemParams(component).paramsForExportFile(); + } + + // All `components` should be installed + syncComponentsInstallation(components) { + if(!this.isDesktop) return; + var data = components.map((component) => { + return this.convertComponentForTransmission(component); + }) + this.installationSyncHandler(data); + } + + /* Used to resolve "sn://" */ + desktop_setApplicationDataPath(path) { + this.applicationDataPath = path; + } + + desktop_setComponentInstallationSyncHandler(handler) { + this.installationSyncHandler = handler; + } + + desktop_setOfflineComponentInstallationHandler(handler) { + this.componentInstallationHandler = handler; + } + desktop_setInitialDataLoadHandler(handler) { this.dataLoadHandler = handler; if(this.dataLoaded) { diff --git a/app/assets/javascripts/app/services/directives/views/editorMenu.js b/app/assets/javascripts/app/services/directives/views/editorMenu.js index 008c5fadc..26a8de3f7 100644 --- a/app/assets/javascripts/app/services/directives/views/editorMenu.js +++ b/app/assets/javascripts/app/services/directives/views/editorMenu.js @@ -16,10 +16,16 @@ class EditorMenu { $scope.editors = componentManager.componentsForArea("editor-editor"); + $scope.isDesktop = isDesktopApplication(); + $scope.selectEditor = function($event, editor) { if(editor) { editor.conflict_of = null; // clear conflict if applicable } + if(editor.local && !isDesktopApplication()) { + alert("This editor is installed ") + return; + } $scope.callback()(editor); } diff --git a/app/assets/javascripts/app/services/directives/views/roomBar.js b/app/assets/javascripts/app/services/directives/views/roomBar.js index 53169c393..ff492e739 100644 --- a/app/assets/javascripts/app/services/directives/views/roomBar.js +++ b/app/assets/javascripts/app/services/directives/views/roomBar.js @@ -7,10 +7,9 @@ class RoomBar { }; } - controller($rootScope, $scope, extensionManager, syncManager, modelManager, componentManager, $timeout) { + controller($rootScope, $scope, desktopManager, syncManager, modelManager, componentManager, $timeout) { 'ngInject'; - $scope.extensionManager = extensionManager; $scope.componentManager = componentManager; $rootScope.$on("initial-data-loaded", () => { @@ -20,8 +19,48 @@ class RoomBar { }) }); + componentManager.registerHandler({identifier: "roomBar", areas: ["rooms"], activationHandler: function(component){ + if(component.active) { + $timeout(function(){ + var iframe = componentManager.iframeForComponent(component); + if(iframe) { + iframe.onload = function() { + componentManager.registerComponentWindow(component, iframe.contentWindow); + }.bind(this); + } + }.bind(this)); + } + }.bind(this), actionHandler: function(component, action, data){ + if(action === "set-size") { + console.log("Set size event", data); + var setSize = function(element, size) { + var widthString = typeof size.width === 'string' ? size.width : `${data.width}px`; + var heightString = typeof size.height === 'string' ? size.height : `${data.height}px`; + element.setAttribute("style", `width:${widthString}; height:${heightString}; `); + } + + if(data.type === "content") { + var iframe = componentManager.iframeForComponent(component); + var width = data.width; + var height = data.height; + iframe.width = width; + iframe.height = height; + + setSize(iframe, data); + } else { + var container = document.getElementById("room-" + component.uuid); + setSize(container, data); + } + } + }.bind(this)}); + $scope.selectRoom = function(room) { - + room.show = !room.show; + if(room.show) { + this.componentManager.activateComponent(room); + } else { + this.componentManager.deactivateComponent(room); + } } } diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index e3e856325..7dc364919 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -90,9 +90,32 @@ h2 { padding-left: 10px; padding-right: 10px; margin-left: 5px; + position: relative; .room-item { display: inline-block; + position: relative; + + .room-container { + max-height: 85vh; + position: absolute; + right: 0px; + bottom: 20px; + min-width: 300px; + z-index: 1000; + margin-top: 15px; + + box-shadow: 0px 0px 15px rgba(black, 0.2); + border: none; + cursor: default; + overflow: auto; + background-color: white; + } + + .room-iframe { + width: 100%; + height: 100%; + } } } } diff --git a/app/assets/stylesheets/app/_menus.scss b/app/assets/stylesheets/app/_menus.scss index e1bfbd4af..8e851aad6 100644 --- a/app/assets/stylesheets/app/_menus.scss +++ b/app/assets/stylesheets/app/_menus.scss @@ -112,7 +112,6 @@ ul.section-menu-bar { cursor: pointer; } - .shortcut { float: right; font-size: 12px; @@ -122,6 +121,12 @@ ul.section-menu-bar { padding-right: 10px; } } + + .sublabel { + font-size: 12px; + font-weight: normal; + margin-top: 5px; + } } diff --git a/app/assets/stylesheets/app/_ui.scss b/app/assets/stylesheets/app/_ui.scss index fcbaa03eb..bbad720ee 100644 --- a/app/assets/stylesheets/app/_ui.scss +++ b/app/assets/stylesheets/app/_ui.scss @@ -101,14 +101,14 @@ button:focus {outline:0;} } .panel { - position: absolute; - right: 0px; - min-width: 300px; - z-index: 1000; - margin-top: 10px; - box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.2); - border: none; - background-color: white; + position: absolute; + right: 0px; + min-width: 300px; + z-index: 1000; + margin-top: 10px; + box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.2); + border: none; + background-color: white; } .panel-right { diff --git a/app/assets/templates/frontend/directives/editor-menu.html.haml b/app/assets/templates/frontend/directives/editor-menu.html.haml index cad2f2d0b..97e11de66 100644 --- a/app/assets/templates/frontend/directives/editor-menu.html.haml +++ b/app/assets/templates/frontend/directives/editor-menu.html.haml @@ -9,10 +9,12 @@ %div{"ng-if" => "editors.length > 0"} .header .title External Editors - .subtitle Can access your current note decrypted. %ul %li.menu-item{"ng-repeat" => "editor in editors", "ng-click" => "selectEditor($event, editor)"} %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy %label.menu-item-title %span.inline.tinted.mr-10{"ng-if" => "selectedEditor === editor"} ✓ {{editor.name}} + .sublabel{"ng-if" => "editor.local"} + Locally Installed + .sublabel.faded{"ng-if" => "editor.local && !isDesktop"} Unavailable in Web Browser diff --git a/app/assets/templates/frontend/directives/room-bar.html.haml b/app/assets/templates/frontend/directives/room-bar.html.haml index fbb5ecad3..4d671b109 100644 --- a/app/assets/templates/frontend/directives/room-bar.html.haml +++ b/app/assets/templates/frontend/directives/room-bar.html.haml @@ -1,2 +1,4 @@ .room-item{"ng-repeat" => "room in rooms", "ng-click" => "selectRoom(room)"} - {{room.name}} + %span {{room.name}} + .room-container.panel-right{"ng-if" => "room.show && room.active", "ng-attr-id" => "room-{{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/app/assets/templates/frontend/editor.html.haml b/app/assets/templates/frontend/editor.html.haml index a33cb2a5a..7bb9d071b 100644 --- a/app/assets/templates/frontend/editor.html.haml +++ b/app/assets/templates/frontend/editor.html.haml @@ -9,7 +9,7 @@ .editor-tags #note-tags-component-container{"ng-if" => "ctrl.tagsComponent && ctrl.tagsComponent.active"} - %iframe#note-tags-iframe{"ng-src" => "{{ctrl.tagsComponent.url | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts", "data-component-id" => "{{ctrl.tagsComponent.uuid}}"} + %iframe#note-tags-iframe{"ng-src" => "{{ctrl.componentManager.urlForComponent(ctrl.tagsComponent) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts", "data-component-id" => "{{ctrl.tagsComponent.uuid}}"} %input.tags-input{"ng-if" => "!(ctrl.tagsComponent && ctrl.tagsComponent.active)", "type" => "text", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur();", "ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"} %ul.section-menu-bar{"ng-if" => "ctrl.note"} @@ -46,7 +46,7 @@ %contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"} .editor-content{"ng-if" => "ctrl.noteReady && !ctrl.note.errorDecrypting", "ng-class" => "{'fullscreen' : ctrl.fullscreen }"} - %iframe#editor-iframe{"ng-if" => "ctrl.editorComponent && ctrl.editorComponent.active", "ng-src" => "{{ctrl.editorComponent.url | trusted}}", "data-component-id" => "{{ctrl.editorComponent.uuid}}", "frameBorder" => "0", "style" => "width: 100%;"} + %iframe#editor-iframe{"ng-if" => "ctrl.editorComponent && ctrl.editorComponent.active", "ng-src" => "{{ctrl.componentManager.urlForComponent(ctrl.editorComponent) | trusted}}", "data-component-id" => "{{ctrl.editorComponent.uuid}}", "frameBorder" => "0", "style" => "width: 100%;"} Loading %textarea.editable#note-text-editor{"ng-if" => "!ctrl.editorComponent", "ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-model" => "ctrl.note.text", "ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto"} @@ -59,4 +59,4 @@ #editor-pane-component-stack .component.component-stack-border{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "ng-show" => "!component.ignoreEvents", "id" => "{{'component-' + component.uuid}}", "ng-mouseover" => "component.showExit = true", "ng-mouseleave" => "component.showExit = false"} .exit-button.body-text-color{"ng-if" => "component.showExit", "ng-click" => "ctrl.disableComponentForCurrentItem(component, true)"} × - %iframe#note-tags-iframe{"ng-src" => "{{component.url | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{component.uuid}}"} + %iframe#note-tags-iframe{"ng-src" => "{{ctrl.componentManager.urlForComponent(component) | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{component.uuid}}"} diff --git a/app/assets/templates/frontend/tags.html.haml b/app/assets/templates/frontend/tags.html.haml index 4b689860c..5a5a4a807 100644 --- a/app/assets/templates/frontend/tags.html.haml +++ b/app/assets/templates/frontend/tags.html.haml @@ -1,5 +1,5 @@ .section.tags#tags-column - %iframe#tags-list-iframe{"ng-if" => "ctrl.component && ctrl.component.active", "ng-src" => "{{ctrl.component.url | trusted}}", "frameBorder" => "0", "style" => "width: 100%; height: 100%;", "sandbox" => "allow-scripts"} + %iframe#tags-list-iframe{"ng-if" => "ctrl.component && ctrl.component.active", "ng-src" => "{{ctrl.componentManager.urlForComponent(ctrl.component) | trusted}}", "frameBorder" => "0", "style" => "width: 100%; height: 100%;", "sandbox" => "allow-scripts"} #tags-content.content{"ng-if" => "!(ctrl.component && ctrl.component.active)"} #tags-title-bar.section-title-bar .title Tags