From 7cba67ce690f4da863c8bebd9e8f33f52419eead Mon Sep 17 00:00:00 2001 From: Mo Bitar Date: Wed, 12 Dec 2018 18:37:07 -0600 Subject: [PATCH] Dock shortcuts --- .../javascripts/app/controllers/footer.js | 57 +++++++++++++++++++ .../app/directives/functional/elemReady.js | 15 +++++ .../app/directives/views/componentView.js | 1 + .../app/services/componentManager.js | 31 ++++++---- app/assets/stylesheets/app/_footer.scss | 17 +++++- app/assets/templates/footer.html.haml | 11 ++++ 6 files changed, 120 insertions(+), 12 deletions(-) create mode 100644 app/assets/javascripts/app/directives/functional/elemReady.js diff --git a/app/assets/javascripts/app/controllers/footer.js b/app/assets/javascripts/app/controllers/footer.js index 5aeb5b176..f0c37e9a8 100644 --- a/app/assets/javascripts/app/controllers/footer.js +++ b/app/assets/javascripts/app/controllers/footer.js @@ -138,11 +138,68 @@ angular.module('app') this.componentManager = componentManager; this.rooms = []; + this.themes = []; modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => { this.rooms = modelManager.components.filter((candidate) => {return candidate.area == "rooms" && !candidate.deleted}); }); + modelManager.addItemSyncObserver("footer-bar-themes", "SN|Theme", (allItems, validItems, deletedItems, source) => { + let themes = modelManager.validItemsForContentType("SN|Theme").filter((candidate) => {return !candidate.deleted}).sort((a, b) => { + return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1; + }); + + let differ = themes.length != this.themes.length; + + this.themes = themes; + + if(differ) { + this.reloadDockShortcuts(); + } + }); + + this.reloadDockShortcuts = function() { + let shortcuts = []; + for(var theme of this.themes) { + var icon = theme.content.package_info.dock_icon; + if(!icon) { + continue; + } + shortcuts.push({ + component: theme, + icon: icon + }) + } + + this.dockShortcuts = shortcuts.sort((a, b) => { + // circles first, then images + + var aType = a.icon.type; + var bType = b.icon.type; + + if(aType == bType) { + return 0; + } else if(aType == "circle" && bType == "svg") { + return -1; + } else if(bType == "circle" && aType == "svg") { + return 1; + } + }); + } + + this.initSvgForShortcut = function(shortcut) { + var id = "dock-svg-" + shortcut.component.uuid; + var element = document.getElementById(id); + var parser = new DOMParser(); + var svg = shortcut.component.content.package_info.dock_icon.source; + var doc = parser.parseFromString(svg, "image/svg+xml"); + element.appendChild(doc.documentElement); + } + + this.selectShortcut = function(shortcut) { + componentManager.toggleComponent(shortcut.component); + } + componentManager.registerHandler({identifier: "roomBar", areas: ["rooms", "modal"], activationHandler: (component) => { // RIP: There used to be code here that checked if component.active was true, and if so, displayed the component. // However, we no longer want to persist active state for footer extensions. If you open Extensions on one computer, diff --git a/app/assets/javascripts/app/directives/functional/elemReady.js b/app/assets/javascripts/app/directives/functional/elemReady.js new file mode 100644 index 000000000..7eef56fa7 --- /dev/null +++ b/app/assets/javascripts/app/directives/functional/elemReady.js @@ -0,0 +1,15 @@ +angular +.module('app') +.directive( 'elemReady', function( $parse ) { + return { + restrict: 'A', + link: function( $scope, elem, attrs ) { + elem.ready(function(){ + $scope.$apply(function(){ + var func = $parse(attrs.elemReady); + func($scope); + }) + }) + } + } +}) diff --git a/app/assets/javascripts/app/directives/views/componentView.js b/app/assets/javascripts/app/directives/views/componentView.js index efe212198..459fdb3ad 100644 --- a/app/assets/javascripts/app/directives/views/componentView.js +++ b/app/assets/javascripts/app/directives/views/componentView.js @@ -81,6 +81,7 @@ class ComponentView { // Add small timeout to, as $scope.loading controls loading overlay, // which is used to avoid flicker when enabling extensions while having an enabled theme + // we don't use ng-show because it causes problems with rendering iframes after timeout, for some reason. $timeout(() => { $scope.loading = false; $scope.issueLoading = false; diff --git a/app/assets/javascripts/app/services/componentManager.js b/app/assets/javascripts/app/services/componentManager.js index b6a2bba0b..ccb255d4e 100644 --- a/app/assets/javascripts/app/services/componentManager.js +++ b/app/assets/javascripts/app/services/componentManager.js @@ -631,22 +631,33 @@ class ComponentManager { } handleToggleComponentMessage(sourceComponent, targetComponent, message) { - if(targetComponent.area == "modal") { - this.openModalComponent(targetComponent); + this.toggleComponent(targetComponent); + } + + toggleComponent(component) { + if(component.area == "modal") { + this.openModalComponent(component); } else { - if(targetComponent.active) { - this.deactivateComponent(targetComponent); + if(component.active) { + this.deactivateComponent(component); } else { - if(targetComponent.content_type == "SN|Theme" && !targetComponent.isLayerable()) { + if(component.content_type == "SN|Theme") { // Deactive currently active theme if new theme is not layerable var activeThemes = this.getActiveThemes(); - for(var theme of activeThemes) { - if(theme && !theme.isLayerable()) { - this.deactivateComponent(theme); - } + + // Activate current before deactivating others, so as not to flicker + this.activateComponent(component); + + if(!component.isLayerable()) { + setTimeout(() => { + for(var theme of activeThemes) { + if(theme && !theme.isLayerable()) { + this.deactivateComponent(theme); + } + } + }, 10); } } - this.activateComponent(targetComponent); } } } diff --git a/app/assets/stylesheets/app/_footer.scss b/app/assets/stylesheets/app/_footer.scss index f03c27adb..e5d46fe1f 100644 --- a/app/assets/stylesheets/app/_footer.scss +++ b/app/assets/stylesheets/app/_footer.scss @@ -21,6 +21,20 @@ z-index: $z-index-footer-bar-item-panel; margin-top: 15px; } + + &.dock-shortcut:hover .sk-app-bar-item-column { + border-bottom: 2px solid var(--sn-stylekit-info-color); + } + + svg { + width: 12px; + height: 12px; + fill: var(--sn-stylekit-foreground-color); + + &:hover { + fill: var(--sn-stylekit-info-color); + } + } } #account-panel { @@ -41,6 +55,5 @@ a.disabled { #footer-lock-icon { margin-left: 5px; - padding-left: 8px; - border-left: 1px solid gray; + padding-left: 5px; } diff --git a/app/assets/templates/footer.html.haml b/app/assets/templates/footer.html.haml index 40717084e..31192f7eb 100644 --- a/app/assets/templates/footer.html.haml +++ b/app/assets/templates/footer.html.haml @@ -1,6 +1,7 @@ .sn-component #footer-bar.sk-app-bar.no-edges .left + .sk-app-bar-item{"ng-click" => "ctrl.accountMenuPressed()", "click-outside" => "ctrl.clickOutsideAccountMenu()", "is-open" => "ctrl.showAccountMenu"} .sk-app-bar-item-column .sk-circle.small{"ng-class" => "ctrl.error ? 'danger' : (ctrl.getUser() ? 'info' : 'neutral')"} @@ -39,6 +40,16 @@ .sk-app-bar-item{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"} .sk-label Refresh + .sk-app-bar-item.border{"ng-if" => "ctrl.dockShortcuts.length > 0"} + + .sk-app-bar-item.dock-shortcut{"ng-repeat" => "shortcut in ctrl.dockShortcuts"} + .sk-app-bar-item-column{"ng-click" => "ctrl.selectShortcut(shortcut)", "ng-class" => "{'underline': shortcut.component.active}"} + .div{"ng-if" => "shortcut.icon.type == 'circle'"} + .sk-circle.small{"ng-style" => "{'background-color': shortcut.icon.background_color, 'border-color': shortcut.icon.border_color}"} + .div{"ng-if" => "shortcut.icon.type == 'svg'"} + .svg-item{"ng-attr-id" => "dock-svg-{{shortcut.component.uuid}}", "elem-ready" => "ctrl.initSvgForShortcut(shortcut)"} + + .sk-app-bar-item.border{"ng-if" => "ctrl.hasPasscode()"} .sk-app-bar-item#lock-item{"ng-if" => "ctrl.hasPasscode()", "title" => "Locks application and wipes unencrypted data from memory."} .sk-label %i.icon.ion-locked#footer-lock-icon{"ng-if" => "ctrl.hasPasscode()", "ng-click" => "ctrl.lockApp()"}