diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1737bc1..e4d4d8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,23 @@
+# 1.34.0 (07/07/2016)
+
+## Features
+
+ - Dashboard Sidebar: Added a new sidebar with a toolbar of relevent links, ability to show/hide Widgets, and space for Dashboard-defined content to appear. Dashboards can use this space to provide useful information about the data or visualizations, or to add controls, filters, etc.
+
+ - Cyclotron.functions.forceUpdate: Added method to manually trigger an Angular.js digest cycle.
+
+ - Upgraded Moment.js to 2.13.0
+
+## Bug Fixes
+
+ - Dashboard Performance: improved dashboard performance across the board
+
+ - Table Widget: fixed broken Freeze Headers functionality
+
+ - Header Widget: when displaying page name in the header, value was always blank
+
+ - Fixed Numeral formatting errors with non-numeric strings; added unit tests
+
# 1.33.0 (06/23/2016)
## Features
diff --git a/cyclotron-site/app/partials/dashboard.jade b/cyclotron-site/app/partials/dashboard.jade
index 70ad369..ca80902 100644
--- a/cyclotron-site/app/partials/dashboard.jade
+++ b/cyclotron-site/app/partials/dashboard.jade
@@ -1,6 +1,10 @@
//- Dashboards
-div.dashboard
+div.dashboard.dashboard-page-background(ng-class='{ "has-sidebar": dashboard.sidebar.showDashboardSidebar }')
+
+ .click-cover
+
+ include dashboardSidebar
.dashboard-controls(ng-hide='dashboard.showDashboardControls == false')
i.fa.fa-chevron-left(ng-click='moveBack()', ng-class='{ disabled: !canMoveBack() }', title='Go to the previous page')
@@ -12,7 +16,7 @@ div.dashboard
i.fa.fa-chevron-right(ng-click='moveForward()', ng-class='{ disabled: !canMoveForward() }', title='Go to the next page')
.dashboard-pages
- div(dashboard-page, dashboard='dashboard',
- page='page', page-number='{{ currentPageIndex }}', page-overrides='dashboardOverrides.pages[currentPageIndex]',
- ng-repeat='page in currentPage')
+ div(dashboard-page, ng-repeat='page in currentPage',
+ dashboard='dashboard', page='page', page-number='{{ currentPageIndex }}',
+ page-overrides='dashboardOverrides.pages[currentPageIndex]')
diff --git a/cyclotron-site/app/partials/dashboardSidebar.jade b/cyclotron-site/app/partials/dashboardSidebar.jade
new file mode 100644
index 0000000..e53e675
--- /dev/null
+++ b/cyclotron-site/app/partials/dashboardSidebar.jade
@@ -0,0 +1,42 @@
+.dashboard-sidebar.collapsed.dashboard-page-background(ng-if='dashboard.sidebar.showDashboardSidebar')
+ .sidebar-expander-hitbox
+ .sidebar-expander
+ i.fa.fa-caret-right
+
+ .sidebar-header
+ h1(ng-if='dashboard.sidebar.showDashboardTitle == true') {{ dashboardDisplayName }}
+ table.iconbar(ng-if='dashboard.sidebar.showToolbar == true')
+ tr
+ td
+ a(ng-href='/edit/{{ dashboard.name }}', title='Edit this Dashboard', target='_blank')
+ i.fa.fa-edit
+ td(ng-if='analyticsEnabled()')
+ a(ng-href='/analytics/{{ dashboard.name }}', title='View Analytics for this Dashboard', target='_blank')
+ i.fa.fa-bar-chart
+ td
+ a(ng-href='{{ exportUrl }}', title='Export this Dashboard', target='_blank')
+ i.fa.fa-download
+ td(requires-auth)
+ i.fa.fa-thumbs-o-up(ng-if='!isLiked', ng-click='toggleLike()', title='Like this Dashboard')
+ i.fa.fa-thumbs-up(ng-if='isLiked', ng-click='toggleLike()', title='Unlike this Dashboard')
+
+ .sidebar-accordion
+
+ accordion-group(ng-repeat='content in dashboard.sidebar.sidebarContent track by $index', heading='{{ content.heading }}')
+ div(ng-bind-html='trustHtml(content.html)')
+
+ accordion-group(heading='Show/Hide Widgets', ng-if='dashboard.sidebar.showHideWidgets == true')
+ table.table
+ tr(ng-repeat='widget in widgetVisibilities track by $index')
+ td {{ widget.label }}
+ td
+ switch(ng-model='widget.visible', ng-change='changeVisibility(widget, $index)')
+ p.centered
+ a(ng-click='resetDashboardOverrides()', title='Reset to default')
+ i.fa.fa-refresh
+ | Reset to default
+ .sidebar-footer
+ .logos
+ a.logo(ng-repeat='logo in footerLogos', title='{{ logo.title }}',
+ ng-href='{{ logo.href }}', target='_self')
+ img(ng-src='{{ logo.src }}', alt='{{ logo.title }}')
diff --git a/cyclotron-site/app/partials/header.jade b/cyclotron-site/app/partials/header.jade
index fd38b55..ece4448 100644
--- a/cyclotron-site/app/partials/header.jade
+++ b/cyclotron-site/app/partials/header.jade
@@ -9,7 +9,7 @@
i.fa.fa-file-o
a.documentation(ui-sref='help', target='_blank' title='Documentation for Cyclotron')
i.fa.fa-question-circle
- a.analytics(ui-sref='analytics', title='Analytics for Cyclotron')
+ a.analytics(ng-if='analyticsEnabled()', ui-sref='analytics', title='Analytics for Cyclotron')
i.fa.fa-bar-chart
a.login(requires-auth, ng-click='login()', ng-if='!isLoggedIn()', title='Login')
i.fa.fa-unlock
diff --git a/cyclotron-site/app/partials/help/3rdparty.jade b/cyclotron-site/app/partials/help/3rdparty.jade
index c2734bc..a5448b4 100644
--- a/cyclotron-site/app/partials/help/3rdparty.jade
+++ b/cyclotron-site/app/partials/help/3rdparty.jade
@@ -28,7 +28,7 @@ table
tr
td
a(href='http://momentjs.com/', target='_blank') Moment.js
- td 2.8.4
+ td 2.13.0
td Parse, validate, manipulate, and display dates in javascript
tr
td
diff --git a/cyclotron-site/app/partials/help/javascriptApi.jade b/cyclotron-site/app/partials/help/javascriptApi.jade
index 21ac99d..ec9b4b8 100644
--- a/cyclotron-site/app/partials/help/javascriptApi.jade
+++ b/cyclotron-site/app/partials/help/javascriptApi.jade
@@ -35,6 +35,18 @@ table
td Cyclotron.getDeeplink()
td Returns a deeplink URL to the current Dashboard, including the values of all Parameters
+h4 Functions
+
+p These functions are hooks into Cyclotron that can be leveraged by Dashboards
+
+table
+ tr
+ th Function
+ th Description
+ tr
+ td Cyclotron.functions.forceUpdate()
+ td Forces Cyclotron to do an internal update of Dashboard state, e.g. syncing parameters to the URL. In general, this should not be needed, but can be used to immediately trigger an update cycle after running custom JavaScript.
+
h4 Built-In Parameters
p These Parameters are built-in to every Dashboard, and appear in the URL when set. They don't have to be configured manually in the Parameters section of the Dashboard, but they can be added there in order to change the default value.
diff --git a/cyclotron-site/app/partials/home.jade b/cyclotron-site/app/partials/home.jade
index 0620193..fb72c2b 100644
--- a/cyclotron-site/app/partials/home.jade
+++ b/cyclotron-site/app/partials/home.jade
@@ -37,7 +37,7 @@
a.documentation(ui-sref='help', title='Documentation for Cyclotron')
i.fa.fa-question-circle
span Help
- a.login(ui-sref='analytics', title='Analytics for Cyclotron')
+ a.login(ng-if='analyticsEnabled()', ui-sref='analytics', title='Analytics for Cyclotron')
i.fa.fa-bar-chart
span Analytics
a.login(requires-auth, ng-if='!isLoggedIn()', ng-click='login()',title='Login')
diff --git a/cyclotron-site/app/partials/sidebarAccordion.jade b/cyclotron-site/app/partials/sidebarAccordion.jade
new file mode 100644
index 0000000..34f4e04
--- /dev/null
+++ b/cyclotron-site/app/partials/sidebarAccordion.jade
@@ -0,0 +1 @@
+.panel-group(ng-transclude='')
diff --git a/cyclotron-site/app/partials/sidebarAccordionGroup.jade b/cyclotron-site/app/partials/sidebarAccordionGroup.jade
new file mode 100644
index 0000000..dd3d3d0
--- /dev/null
+++ b/cyclotron-site/app/partials/sidebarAccordionGroup.jade
@@ -0,0 +1,8 @@
+.panel.panel-default
+ .panel-heading(ng-click='toggleOpen()')
+ h4.panel-title
+ a.accordion-toggle(accordion-transclude='heading')
+ span(ng-class='{"text-muted": isDisabled}') {{heading}}
+
+ .panel-collapse(uib-collapse='!isOpen')
+ .panel-body(ng-style='styles', ng-transclude='')
diff --git a/cyclotron-site/app/scripts/common/app.coffee b/cyclotron-site/app/scripts/common/app.coffee
index 92d161c..7c1ab44 100644
--- a/cyclotron-site/app/scripts/common/app.coffee
+++ b/cyclotron-site/app/scripts/common/app.coffee
@@ -48,9 +48,10 @@ cyclotronApp = angular.module 'cyclotronApp', [
'ui.bootstrap'
'ui.ace'
'drahak.hotkeys'
+ 'googlechart'
'LocalForageModule'
'tableSort'
- 'googlechart'
+ 'uiSwitch'
]
cyclotronDirectives = angular.module 'cyclotronApp.directives', []
@@ -309,7 +310,7 @@ cyclotronApp.config ($stateProvider, $urlRouterProvider, $locationProvider, $con
}
$locationProvider.hashPrefix = '!'
-cyclotronApp.run ($rootScope, $urlRouter, $location, $state, $stateParams, $uibModal, userService) ->
+cyclotronApp.run ($rootScope, $urlRouter, $location, $state, $stateParams, $uibModal, configService, userService) ->
#
# Authentication-related scope variables
@@ -318,6 +319,8 @@ cyclotronApp.run ($rootScope, $urlRouter, $location, $state, $stateParams, $uibM
$rootScope.isAdmin = userService.isAdmin
$rootScope.currentUser = userService.currentUser
+ $rootScope.analyticsEnabled = -> configService.enableAnalytics
+
$rootScope.login = (isModal = false) ->
options =
templateUrl: '/partials/login.html'
diff --git a/cyclotron-site/app/scripts/common/mixins.coffee b/cyclotron-site/app/scripts/common/mixins.coffee
index 15e432e..d12903f 100644
--- a/cyclotron-site/app/scripts/common/mixins.coffee
+++ b/cyclotron-site/app/scripts/common/mixins.coffee
@@ -168,7 +168,8 @@ _.mixin({
return 'NaN' if _.isNaN value
if !_.isNumber(value)
- value = parseFloat(value)
+ parsedValue = parseFloat(value)
+ if _.isNaN parsedValue then return value else value = parsedValue
return numeral(value).format(format)
# ngApply: Takes a scope and a function, and returns a function that calls $apply around the given function
diff --git a/cyclotron-site/app/scripts/common/services/services.commonConfigService.coffee b/cyclotron-site/app/scripts/common/services/services.commonConfigService.coffee
index 0cc5b98..20393a1 100644
--- a/cyclotron-site/app/scripts/common/services/services.commonConfigService.coffee
+++ b/cyclotron-site/app/scripts/common/services/services.commonConfigService.coffee
@@ -26,7 +26,7 @@ cyclotronServices.factory 'commonConfigService', ->
exports = {
- version: '1.33.0'
+ version: '1.34.0'
logging:
enableDebug: false
@@ -171,6 +171,67 @@ cyclotronServices.factory 'commonConfigService', ->
default: true
defaultHidden: true
+ sidebar:
+ label: 'Sidebar'
+ description: ''
+ type: 'propertyset'
+ default: {}
+ defaultHidden: true
+ properties:
+ showDashboardSidebar:
+ label: 'Show Dashboard Sidebar'
+ description: 'If false, hides the default Dashboard Sidebar.'
+ type: 'boolean'
+ required: false
+ default: false
+ order: 10
+ showDashboardTitle:
+ label: 'Include Dashboard Title'
+ description: 'Enables a section of the sidebar for the Dashboard title.'
+ type: 'boolean'
+ required: false
+ default: true
+ defaultHidden: true
+ order: 11
+ showToolbar:
+ label: 'Include Toolbar'
+ description: 'Enables a toolbar in the sidebar.'
+ type: 'boolean'
+ required: false
+ default: true
+ defaultHidden: true
+ order: 12
+ showHideWidgets:
+ label: 'Include Show/Hide Widgets'
+ description: 'Enables a section of the sidebar for overriding the visibility of Widgets.'
+ type: 'boolean'
+ required: false
+ default: false
+ defaultHidden: true
+ order: 13
+ sidebarContent:
+ label: 'Custom Sidebar Sections'
+ singleLabel: 'Section'
+ description: 'One or more sections of content to display in the Sidebar.'
+ type: 'propertyset[]'
+ default: []
+ order: 15
+ properties:
+ heading:
+ label: 'Heading'
+ description: 'Heading for the Sidebar section.'
+ type: 'string'
+ inlineJs: true
+ order: 1
+ html:
+ label: 'HTML Content'
+ description: 'HTML Content to display.'
+ placeholder: 'Value'
+ type: 'editor'
+ editorMode: 'html'
+ inlineJs: true
+ order: 2
+
pages:
label: 'Pages'
description: 'The list of Page definitions which compose the Dashboard.'
@@ -1065,6 +1126,17 @@ cyclotronServices.factory 'commonConfigService', ->
sample:
name: ''
+ sidebar:
+ showDashboardSidebar: true
+
+ # Dashboard Sidebar
+ dashboardSidebar:
+ footer:
+ logos: [{
+ title: 'Cyclotron'
+ src: '/img/favicon32.png'
+ href: '/'
+ }]
# List of help pages
help: [
diff --git a/cyclotron-site/app/scripts/common/services/services.dashboardService.coffee b/cyclotron-site/app/scripts/common/services/services.dashboardService.coffee
index 25481a3..ffba3b5 100644
--- a/cyclotron-site/app/scripts/common/services/services.dashboardService.coffee
+++ b/cyclotron-site/app/scripts/common/services/services.dashboardService.coffee
@@ -599,6 +599,16 @@ cyclotronServices.factory 'dashboardService', ($http, $resource, $q, analyticsSe
else
pageName
+ service.getWidgetName = (widget, index) ->
+ if !widget.widget? || widget.widget == ''
+ return 'Widget ' + (index + 1)
+ if widget?.name?.length > 0
+ return _.titleCase(widget.widget) + ': ' + widget.name
+ else if widget.title?.length > 0
+ return _.titleCase(widget.widget) + ': ' + widget.title
+ else
+ return _.titleCase(widget.widget)
+
service.getVisitCategory = (dashboard) ->
return null unless dashboard?
visits = dashboard.visits
diff --git a/cyclotron-site/app/scripts/dashboards/controller.dashboard.coffee b/cyclotron-site/app/scripts/dashboards/controller.dashboard.coffee
index 18f1f5c..dba5830 100644
--- a/cyclotron-site/app/scripts/dashboards/controller.dashboard.coffee
+++ b/cyclotron-site/app/scripts/dashboards/controller.dashboard.coffee
@@ -17,7 +17,7 @@
#
# Home controller.
#
-cyclotronApp.controller 'DashboardController', ($scope, $stateParams, $localForage, $location, $timeout, $window, $q, $uibModal, analyticsService, configService, cyclotronDataService, dashboardService, dataService, loadService, logService, parameterService, userService) ->
+cyclotronApp.controller 'DashboardController', ($scope, $stateParams, $localForage, $location, $timeout, $window, $q, $uibModal, analyticsService, configService, cyclotronDataService, dashboardOverridesService, dashboardService, dataService, loadService, logService, parameterService, userService) ->
preloadTimer = null
rotateTimer = null
@@ -44,7 +44,8 @@ cyclotronApp.controller 'DashboardController', ($scope, $stateParams, $localFora
$window.Cyclotron =
version: configService.version
dataSources: {}
- functions: {}
+ functions:
+ forceUpdate: -> $scope.$apply()
parameters: _.clone $location.search()
data: cyclotronDataService
@@ -52,8 +53,8 @@ cyclotronApp.controller 'DashboardController', ($scope, $stateParams, $localFora
currentPage = $scope.dashboard.pages[$scope.currentPageIndex]
pageName = dashboardService.getPageName(currentPage, $scope.currentPageIndex)
- displayName = _.jsExec($scope.dashboard.displayName) || $scope.dashboard.name
- loadService.setTitle displayName + ' | ' + pageName + ' | Cyclotron'
+ $scope.dashboardDisplayName = _.jsExec($scope.dashboard.displayName) || $scope.dashboard.name
+ loadService.setTitle $scope.dashboardDisplayName + ' | ' + pageName + ' | Cyclotron'
if currentPage.name? || $scope.currentPageIndex != 0
$location.path '/' + $scope.dashboard.name + '/' + _.slugify pageName
else
@@ -288,16 +289,6 @@ cyclotronApp.controller 'DashboardController', ($scope, $stateParams, $localFora
dashboardService.setDashboardDefaults(dashboard)
$scope.dashboard = dashboard
- # Initialize dashboard overrides
- $scope.dashboardOverrides.pages ?= []
- _.each dashboard.pages, (page, index) ->
- if !$scope.dashboardOverrides.pages[index]?
- $scope.dashboardOverrides.pages.push { widgets: [] }
- $scope.dashboardOverrides.pages[index].widgets ?= []
- _.each page.widgets, (widget, widgetIndex) ->
- if !$scope.dashboardOverrides.pages[index].widgets[widgetIndex]?
- $scope.dashboardOverrides.pages[index].widgets.push {}
-
# Optionally disable analytics
if dashboard.disableAnalytics == true
configService.enableAnalytics = false
@@ -406,55 +397,68 @@ cyclotronApp.controller 'DashboardController', ($scope, $stateParams, $localFora
_.each themes, (theme) ->
loadService.loadCssUrl('/css/app.themes.' + theme + '.css', true)
- # Initialize parameters
- parameterService.initializeParameters($scope.dashboard).then ->
+ # Initialize dashboard overrides
+ dashboardOverridesService.initializeDashboardOverrides($scope.dashboard).then (dashboardOverrides) ->
+ # Store for Dashboard use
+ $window.Cyclotron.dashboardOverrides = $scope.dashboardOverrides = dashboardOverrides
- # Watch querystring for changes
- $scope.$watch (-> $location.search()), handleQueryStringChanges
+ $scope.$watch 'dashboardOverrides', (updatedOverrides, previousOverrides) ->
+ return if _.isEqual updatedOverrides, previousOverrides
+ dashboardOverridesService.saveDashboardOverrides($scope.dashboard, updatedOverrides)
+ , true
- # Watch Parameters for changes
- $scope.$watch (-> $window.Cyclotron.parameters), handleParameterChanges, true
-
- # Preload any data sources with preload: true
- preloadDataSources = _.filter $scope.dashboard.dataSources, { preload: true }
+ $scope.resetDashboardOverrides = ->
+ $window.Cyclotron.dashboardOverrides = $scope.dashboardOverrides = dashboardOverridesService.resetAndExpandOverrides $scope.dashboard
- _.each preloadDataSources, (dataSourceDefinition) ->
- logService.info 'Preloading data source', dataSourceDefinition.name
- dataSource = dataService.get(dataSourceDefinition)
- dataSource.getData(dataSourceDefinition, _.noop, _.noop, _.noop)
- return
+ # Initialize parameters
+ parameterService.initializeParameters($scope.dashboard).then ->
+
+ # Watch querystring for changes
+ $scope.$watch (-> $location.search()), handleQueryStringChanges
+
+ # Watch Parameters for changes
+ $scope.$watch (-> $window.Cyclotron.parameters), handleParameterChanges, true
+
+ # Preload any data sources with preload: true
+ preloadDataSources = _.filter $scope.dashboard.dataSources, { preload: true }
- # Only load if there are any pages
- if $scope.dashboard.pages.length > 0
- # Navigate to a particular page if specified
- if $window.Cyclotron.parameters.page?
- $scope.goToPage parseInt($window.Cyclotron.parameters.page)
- else if !_.isEmpty $scope.originalDashboardPageName
- pageNames = _.pluck $scope.dashboard.pages, 'name'
- pageIndex = _.findIndex pageNames, (name) ->
- name? and _.slugify(name) == $scope.originalDashboardPageName
-
- if pageIndex >= 0
- $scope.goToPage 1 + pageIndex
- else if $scope.originalDashboardPageName.match(/page-\d+$/)
- $scope.goToPage parseInt($scope.originalDashboardPageName.substring(5))
+ _.each preloadDataSources, (dataSourceDefinition) ->
+ logService.info 'Preloading data source', dataSourceDefinition.name
+ dataSource = dataService.get(dataSourceDefinition)
+ dataSource.getData(dataSourceDefinition, _.noop, _.noop, _.noop)
+ return
+
+ # Only load if there are any pages
+ if $scope.dashboard.pages.length > 0
+ # Navigate to a particular page if specified
+ if $window.Cyclotron.parameters.page?
+ $scope.goToPage parseInt($window.Cyclotron.parameters.page)
+ else if !_.isEmpty $scope.originalDashboardPageName
+ pageNames = _.pluck $scope.dashboard.pages, 'name'
+ pageIndex = _.findIndex pageNames, (name) ->
+ name? and _.slugify(name) == $scope.originalDashboardPageName
+
+ if pageIndex >= 0
+ $scope.goToPage 1 + pageIndex
+ else if $scope.originalDashboardPageName.match(/page-\d+$/)
+ $scope.goToPage parseInt($scope.originalDashboardPageName.substring(5))
+ else
+ $scope.goToPage 1
else
$scope.goToPage 1
- else
- $scope.goToPage 1
- $scope.updateUrl()
+ $scope.updateUrl()
- # Start Dashboard Rotation
- if $window.Cyclotron.parameters.autoRotate == "true" or $scope.dashboard.autoRotate == true
- # ?autoRotate=true/false will override the dashboard setting.
- # There is also a dashboard property which can disable rotation.
- # Only enable rotation if there are multiple pages
- if $scope.dashboard.pages.length > 1
- $scope.paused = false
- $scope.rotate()
-
- $scope.firstLoad = false
+ # Start Dashboard Rotation
+ if $window.Cyclotron.parameters.autoRotate == "true" or $scope.dashboard.autoRotate == true
+ # ?autoRotate=true/false will override the dashboard setting.
+ # There is also a dashboard property which can disable rotation.
+ # Only enable rotation if there are multiple pages
+ if $scope.dashboard.pages.length > 1
+ $scope.paused = false
+ $scope.rotate()
+
+ $scope.firstLoad = false
# Initial load - load dashboard and initialize rotation
$scope.reloadInterval = if $window.Cyclotron.parameters.live == 'true' then 1500 else 60000
@@ -466,14 +470,8 @@ cyclotronApp.controller 'DashboardController', ($scope, $stateParams, $localFora
.search $scope.deeplinkOptions
.toString()
- # Load Overrides, then the dashboard
- $localForage.bind($scope, {
- key: 'dashboardOverrides'
- defaultValue: { pages: [] }
- }).then ->
- $window.Cyclotron.dashboardOverrides = $scope.dashboardOverrides
- logService.debug 'Dashboard Overrides: ' + JSON.stringify($scope.dashboardOverrides)
- $scope.loadDashboard().then $scope.initialLoad
+ # Load the dashboard
+ $scope.loadDashboard().then $scope.initialLoad
#
# Hot Key Bindings
diff --git a/cyclotron-site/app/scripts/dashboards/directives/directives.dashboard.coffee b/cyclotron-site/app/scripts/dashboards/directives/directives.dashboard.coffee
new file mode 100644
index 0000000..4b0bb31
--- /dev/null
+++ b/cyclotron-site/app/scripts/dashboards/directives/directives.dashboard.coffee
@@ -0,0 +1,108 @@
+###
+# Copyright (c) 2013-2016 the original author or authors.
+#
+# Licensed under the MIT License (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.opensource.org/licenses/mit-license.php
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+###
+
+#
+# Top-level Dashboard directive
+#
+# Renders a series of Widgets and manages page-level interactivity. Expects the following
+# scope variables:
+# page: Page to render
+# pageOverrides: Overrides for the current page
+# pageNumber: Index of the Page in the Dashboard (zero-indexed)
+# dashboard: Entire Dashboard object
+#
+cyclotronDirectives.directive 'dashboard', ($compile, $window, $timeout, configService, layoutService, logService) ->
+ {
+ restrict: 'AC'
+
+ link: (scope, element, attrs) ->
+ $element = $(element)
+ $dashboardSidebar = $element.children '.dashboard-sidebar'
+ $dashboardControls = $element.children '.dashboard-controls'
+
+ scope.controlTimer = null
+
+ calculateMouseTarget = ->
+ # Get all dimensions and the padding options
+
+ controlOffset = $dashboardControls.offset()
+ controlWidth = $dashboardControls.width()
+ controlHeight = $dashboardControls.height()
+ padX = configService.dashboard.controls.hitPaddingX
+ padY = configService.dashboard.controls.hitPaddingY
+
+ return unless $dashboardControls? and controlOffset?
+
+ scope.controlTarget = {
+ top: controlOffset.top - padY
+ bottom: controlOffset.top + controlHeight + padY
+ left: controlOffset.left - padX
+ right: controlOffset.left + controlWidth + padX
+ }
+
+ makeControlsDisappear = ->
+ $dashboardControls.removeClass 'active'
+
+ makeControlsAppear = _.throttle(->
+ # Make visible
+ $dashboardControls.addClass 'active'
+
+ # Set timer to remove the controls after some delay
+ $timeout.cancel(scope.controlTimer) if scope.controlTimer?
+
+ scope.controlTimer = $timeout(makeControlsDisappear, configService.dashboard.controls.duration)
+ , 500, { leading: true })
+
+ controlHitTest = (event) ->
+ return unless scope.controlTarget?
+ # Abort if outside the target
+ if event.pageX < scope.controlTarget.left ||
+ event.pageX > scope.controlTarget.right ||
+ event.pageY < scope.controlTarget.top ||
+ event.pageY > scope.controlTarget.bottom
+ return
+
+ makeControlsAppear()
+
+ #
+ # Configure Dashboard Controls
+ #
+ calculateMouseTarget()
+
+ #
+ # Bind mousemove event for entire document (remove during $destroy)
+ #
+ $(document).on 'mousemove', controlHitTest
+
+ $(document).on 'scroll', calculateMouseTarget
+
+ $(window).on 'resize', _.debounce(->
+ scope.$apply(calculateMouseTarget)
+ , 500, { leading: false, maxWait: 1000 })
+
+ #
+ # Cleanup
+ #
+ scope.$on '$destroy', ->
+ $(document).off 'mousemove', controlHitTest
+ $(document).off 'scroll', calculateMouseTarget
+ $(window).off 'resize', calculateMouseTarget
+
+ # Cancel timer
+ $timeout.cancel(scope.controlTimer) if scope.controlTimer?
+
+ return
+ }
diff --git a/cyclotron-site/app/scripts/dashboards/directives/directives.dashboardPage.coffee b/cyclotron-site/app/scripts/dashboards/directives/directives.dashboardPage.coffee
index dfe6437..57eecf5 100644
--- a/cyclotron-site/app/scripts/dashboards/directives/directives.dashboardPage.coffee
+++ b/cyclotron-site/app/scripts/dashboards/directives/directives.dashboardPage.coffee
@@ -38,16 +38,16 @@ cyclotronDirectives.directive 'dashboardPage', ($compile, $window, $timeout, con
template: '
' +
'
' +
'
' +
+ ' widget="widget" page="page" page-overrides="pageOverrides" widget-index="$index" layout="layout" dashboard="dashboard" post-layout="postLayout()">
' +
'
'
link: (scope, element, attrs) ->
$element = $(element)
+ $dashboard = $element.parents('.dashboard')
$dashboardPageInner = $element.children('.dashboard-page-inner')
- $dashboardControls = $('.dashboard-controls')
+ $dashboardControls = $dashboard.find '.dashboard-controls'
+ $dashboardSidebar = $dashboard.find '.dashboard-sidebar'
- scope.controlTimer = null
-
masonry = (element, layout) ->
$dashboardPageInner.masonry({
itemSelector: '.dashboard-widgetwrapper'
@@ -55,59 +55,6 @@ cyclotronDirectives.directive 'dashboardPage', ($compile, $window, $timeout, con
gutter: layout.gutter
})
- calculateMouseTarget = ->
- # Get all dimensions and the padding options
-
- controlOffset = $dashboardControls.offset()
- controlWidth = $dashboardControls.width()
- controlHeight = $dashboardControls.height()
- padX = configService.dashboard.controls.hitPaddingX
- padY = configService.dashboard.controls.hitPaddingY
-
- return unless $dashboardControls? and controlOffset?
-
- scope.controlTarget = {
- top: controlOffset.top - padY
- bottom: controlOffset.top + controlHeight + padY
- left: controlOffset.left - padX
- right: controlOffset.left + controlWidth + padX
- }
-
- makeControlsDisappear = ->
- $dashboardControls.removeClass 'active'
-
- makeControlsAppear = _.throttle(->
- # Make visible
- $dashboardControls.addClass 'active'
-
- # Set timer to remove the controls after some delay
- $timeout.cancel(scope.controlTimer) if scope.controlTimer?
-
- scope.controlTimer = $timeout(makeControlsDisappear, configService.dashboard.controls.duration)
- , 500, { leading: true })
-
- controlHitTest = (event) ->
- # Abort if outside the target
- if event.pageX < scope.controlTarget.left ||
- event.pageX > scope.controlTarget.right ||
- event.pageY < scope.controlTarget.top ||
- event.pageY > scope.controlTarget.bottom
- return
-
- makeControlsAppear()
-
- #
- # Configure Dashboard Controls
- #
- calculateMouseTarget()
-
- #
- # Bind mousemove event for entire document (remove during $destroy)
- #
- $(document).on 'mousemove', controlHitTest
-
- $(document).on 'scroll', calculateMouseTarget
-
#
# Watch the dashboard page and update the layout
#
@@ -121,8 +68,10 @@ cyclotronDirectives.directive 'dashboardPage', ($compile, $window, $timeout, con
if (newValue.enableMasonry != false)
masonry(element, scope.layout)
return
-
- scope.layout = layoutService.getLayout(newValue, $($window).width(), $($window).height())
+
+ containerWidth = $dashboard.innerWidth()
+ containerHeight = $($window).height()
+ scope.layout = layoutService.getLayout(newValue, containerWidth, containerHeight)
# Set page margin if defined
if !_.isNullOrUndefined(scope.layout.margin)
@@ -139,31 +88,28 @@ cyclotronDirectives.directive 'dashboardPage', ($compile, $window, $timeout, con
else
$element.parents().removeClass 'fullscreen'
- # Store updated hit target for the dashboard controls
- calculateMouseTarget()
-
-
# Update everything
updateLayout()
- resizeFunction = _.throttle(->
- scope.$apply(updateLayout)
+ resizeFunction = _.throttle(->
+ scope.$apply updateLayout
, 65)
- # Update on window resizing
- $(window).on 'resize', resizeFunction
+ # Update on element resizing
+ $element.on 'resize', resizeFunction
scope.$on '$destroy', ->
- $(window).off 'resize', resizeFunction
+ $element.off 'resize', resizeFunction
# Apply page theme class to dashboard-controls
+ $dashboard.addClass('dashboard-' + newValue.theme)
$dashboardControls.addClass('dashboard-' + newValue.theme)
# Set dashboard background color from theme
themeSettings = configService.dashboard.properties.theme.options[newValue.theme]
if newValue.theme? and themeSettings?
color = themeSettings.dashboardBackgroundColor
- $('.dashboard, html').css('background-color', color)
+ $('html').css('background-color', color)
return
@@ -171,12 +117,6 @@ cyclotronDirectives.directive 'dashboardPage', ($compile, $window, $timeout, con
# Cleanup
#
scope.$on '$destroy', ->
- $(document).off 'mousemove', controlHitTest
- $(document).off 'scroll', calculateMouseTarget
-
- # Cancel timer
- $timeout.cancel(scope.controlTimer) if scope.controlTimer?
-
# Uninitialize Masonry if still present
if $dashboardPageInner.data('masonry')
$dashboardPageInner.masonry('destroy')
diff --git a/cyclotron-site/app/scripts/dashboards/directives/directives.dashboardSidebar.coffee b/cyclotron-site/app/scripts/dashboards/directives/directives.dashboardSidebar.coffee
new file mode 100644
index 0000000..a483b2b
--- /dev/null
+++ b/cyclotron-site/app/scripts/dashboards/directives/directives.dashboardSidebar.coffee
@@ -0,0 +1,116 @@
+###
+# Copyright (c) 2013-2016 the original author or authors.
+#
+# Licensed under the MIT License (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.opensource.org/licenses/mit-license.php
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+###
+
+cyclotronDirectives.directive 'dashboardSidebar', ($timeout, layoutService) ->
+ {
+ restrict: 'EAC'
+ link: (scope, element, attrs) ->
+ # Initial position
+ scope.sidebarExpanded = false
+
+ $element = $(element)
+ $parent = $element.parent()
+ $header = $element.find '.sidebar-header'
+ $accordion = $element.find '.sidebar-accordion'
+ $footer = $element.find '.sidebar-footer'
+ $hitbox = $element.find '.sidebar-expander-hitbox'
+ $expander = $element.find '.sidebar-expander'
+ $expanderIcon = $expander.children 'i'
+ $clickCover = $parent.find '.click-cover'
+
+ scope.$watch 'sidebarExpanded', (expanded) ->
+ if expanded
+ $element.removeClass 'collapsed'
+ $clickCover.css 'display', 'block'
+ $expanderIcon.removeClass 'fa-caret-right'
+ $expanderIcon.addClass 'fa-caret-left'
+ $hitbox.attr 'title', 'Click to collapse the sidebar'
+ else
+ $element.addClass 'collapsed'
+ $clickCover.css 'display', 'none'
+ $expanderIcon.removeClass 'fa-caret-left'
+ $expanderIcon.addClass 'fa-caret-right'
+ $hitbox.attr 'title', 'Click to expand the sidebar'
+
+ $hitbox.on 'click', (event) ->
+ event.preventDefault()
+ scope.$apply ->
+ scope.sidebarExpanded = !scope.sidebarExpanded
+
+ $clickCover.on 'click', (event) ->
+ event.preventDefault()
+ scope.$apply ->
+ scope.sidebarExpanded = false
+
+ # Resize accordion around header/footer
+ sizer = ->
+ $accordion.height($element.outerHeight() - $header.outerHeight() - $footer.outerHeight())
+
+ $element.on 'resize', _.debounce(->
+ scope.$apply sizer
+ , 300, { leading: false, maxWait: 600 })
+
+ # Run in 100ms
+ timer = $timeout sizer, 100
+
+ scope.$on '$destroy', ->
+ $timeout.cancel timer
+ $element.off 'resize'
+
+ return
+
+ controller: ($scope, configService, dashboardService) ->
+ $scope.footerLogos = configService.dashboardSidebar?.footer?.logos || []
+ $scope.widgetVisibilities = []
+ $scope.widgetOverrides = []
+
+ $scope.updateVisibility = ->
+ actualWidgets = $scope.currentPage[0]?.widgets
+ $scope.widgetOverrides = $scope.dashboardOverrides?.pages[$scope.currentPageIndex]?.widgets
+
+ $scope.widgetVisibilities = _.map actualWidgets, (widget, index) ->
+ # Visible by default
+ visible = true
+
+ if $scope.widgetOverrides?[index].hidden?
+ visible = !$scope.widgetOverrides?[index].hidden
+ else if widget.hidden
+ visible = false
+
+ return {
+ label: dashboardService.getWidgetName(widget, index)
+ visible: visible
+ }
+
+ $scope.changeVisibility = (widget, index) ->
+
+ if widget.visible == true
+ $scope.widgetOverrides[index].hidden = false
+ else
+ $scope.widgetOverrides[index].hidden = true
+ return
+
+ $scope.$watch 'currentPage', (currentPage) ->
+ return unless currentPage?.length > 0
+ $scope.updateVisibility()
+ , true
+
+ $scope.$watch 'dashboardOverrides', (dashboardOverrides) ->
+ return unless dashboardOverrides?
+ $scope.updateVisibility()
+ , true
+
+ }
diff --git a/cyclotron-site/app/scripts/dashboards/directives/directives.dashboardWidget.coffee b/cyclotron-site/app/scripts/dashboards/directives/directives.dashboardWidget.coffee
index 898a387..071a6b6 100644
--- a/cyclotron-site/app/scripts/dashboards/directives/directives.dashboardWidget.coffee
+++ b/cyclotron-site/app/scripts/dashboards/directives/directives.dashboardWidget.coffee
@@ -32,6 +32,7 @@ cyclotronDirectives.directive 'dashboardWidget', (layoutService) ->
return
scope.$watch('layout', (layout) ->
+ return unless layout?
# Set the border width if overloaded (otherwise keep theme default)
if layout.borderWidth?
diff --git a/cyclotron-site/app/scripts/dashboards/directives/directives.sidebarAccordion.coffee b/cyclotron-site/app/scripts/dashboards/directives/directives.sidebarAccordion.coffee
new file mode 100644
index 0000000..c916fdb
--- /dev/null
+++ b/cyclotron-site/app/scripts/dashboards/directives/directives.sidebarAccordion.coffee
@@ -0,0 +1,144 @@
+###
+# Copyright (c) 2016 the original author or authors.
+#
+# Licensed under the MIT License (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.opensource.org/licenses/mit-license.php
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+###
+
+#
+# Accordion for Dashboard Sidebar
+# Adapted from http://jsfiddle.net/hanspc/TBz9F/
+#
+
+cyclotronDirectives.directive 'sidebarAccordion', ($sce, $timeout) ->
+ {
+ restrict: 'EAC'
+ controller: ($scope, $attrs) ->
+
+ this.groups = []
+
+ $scope.trustHtml = (html) ->
+ $sce.trustAsHtml(html)
+
+ # Ensure that all the groups in this accordion are closed
+ this.closeOthers = (openGroup) ->
+ angular.forEach this.groups, (group) ->
+ group.isOpen = false unless group == openGroup
+
+ this.calcHeight()
+
+ # Watch for height changes
+ that = this
+ $scope.$watch 'accordionHeight', (value) ->
+ that.calcHeight()
+
+ this.calcHeight = ->
+ height = _.reduce this.groups, (sum, group) ->
+ sum + group.returnHeight()
+ , 0
+
+ that.panelHeight = $scope.getAccordionHeight() - height
+
+ # This is called from the accordion-group directive to add itself to the accordion
+ this.addGroup = (groupScope) ->
+ that = this
+ this.groups.push(groupScope)
+
+ groupScope.$on '$destroy', (event) ->
+ that.removeGroup(groupScope)
+
+ # This is called from the accordion-group directive when to remove itself
+ this.removeGroup = (group) ->
+ index = this.groups.indexOf(group)
+ if index != -1
+ this.groups.splice(index, 1)
+
+ return
+
+ transclude: true,
+ replace: false,
+ templateUrl: '/partials/sidebarAccordion.html'
+ link: (scope, element, attrs) ->
+ scope.getAccordionHeight = -> $(element).height()
+
+ # Track height of accordion
+ sizer = ->
+ scope.accordionHeight = scope.getAccordionHeight()
+
+ $(element).on 'resize', _.throttle(->
+ scope.$apply sizer
+ , 100)
+
+ # Run in 100ms
+ timer = $timeout sizer, 100
+
+ scope.$on '$destroy', ->
+ $timeout.cancel timer
+ $(element).off 'resize'
+ }
+
+cyclotronDirectives.directive 'accordionGroup', ->
+ {
+ require: '^sidebarAccordion'
+ restrict: 'EA'
+ transclude: true
+ replace: true
+ templateUrl: '/partials/sidebarAccordionGroup.html'
+ scope:
+ heading: '@'
+ isOpen: '=?'
+ isDisabled: '=?'
+
+ controller: ->
+ this.setHeading = (element) ->
+ this.heading = element
+
+ link: (scope, element, attrs, accordionController) ->
+ accordionController.addGroup(scope)
+
+ scope.$watch 'isOpen', (value) ->
+ if value
+ accordionController.closeOthers(scope)
+
+ scope.toggleOpen = ->
+ if !scope.isDisabled
+ scope.isOpen = !scope.isOpen
+
+ scope.returnHeight = ->
+ element.find('.panel-heading').outerHeight(true)
+
+ scope.$watch (-> accordionController.panelHeight), (value) ->
+ if value
+ scope.styles =
+ height: accordionController.panelHeight + 'px'
+ }
+
+cyclotronDirectives.directive 'accordionHeading', ->
+ {
+ restrict: 'EA'
+ transclude: true
+ template: ''
+ replace: true
+ require: '^accordionGroup'
+ link: (scope, element, attr, accordionGroupController, transclude) ->
+ accordionGroupController.setHeading(transclude(scope, -> ))
+ }
+
+cyclotronDirectives.directive 'accordionTransclude', ->
+ {
+ require: '^accordionGroup'
+ link: (scope, element, attr, controller) ->
+ scope.$watch (-> controller[attr.accordionTransclude]), (heading) ->
+ if heading
+ element.html ''
+ element.append heading
+ }
diff --git a/cyclotron-site/app/scripts/dashboards/directives/directives.widget.coffee b/cyclotron-site/app/scripts/dashboards/directives/directives.widget.coffee
index b53defb..06fe782 100644
--- a/cyclotron-site/app/scripts/dashboards/directives/directives.widget.coffee
+++ b/cyclotron-site/app/scripts/dashboards/directives/directives.widget.coffee
@@ -34,6 +34,7 @@ cyclotronDirectives.directive 'widget', ($compile, $sce, $window, layoutService)
widgetIndex: '='
layout: '='
dashboard: '='
+ page: '='
pageOverrides: '='
postLayout: '&'
@@ -46,69 +47,8 @@ cyclotronDirectives.directive 'widget', ($compile, $sce, $window, layoutService)
scope.widgetLayout = { }
- # Determine if a Widget should be visible or hidden on the dashboard
- isWidgetHidden = ->
- return false unless scope.widget?
-
- if scope.pageOverrides?.widgets?
- widgetOverrides = scope.pageOverrides.widgets?[scope.widgetIndex]
-
- # If WidgetOverrides.hidden is set true or false, use its value
- if widgetOverrides?.hidden?
- return widgetOverrides.hidden == true
-
- # Else, default to the widget's "hidden" property
- return scope.widget.hidden == true
-
- # Store Widget API for use by Dashboards
- if scope.widget.name?
- $window.Cyclotron.currentPage.widgets[scope.widget.name] = {
- show: ->
- scope.$apply ->
- widgetOverrides = scope.pageOverrides?.widgets?[scope.widgetIndex]
- widgetOverrides.hidden = false
- hide: ->
- scope.$apply ->
- widgetOverrides = scope.pageOverrides?.widgets?[scope.widgetIndex]
- widgetOverrides.hidden = true
- toggleVisibility: ->
- scope.$apply ->
- widgetOverrides = scope.pageOverrides?.widgets?[scope.widgetIndex]
- widgetOverrides.hidden = !widgetOverrides.hidden
- }
-
- # Watch for widget visibility to change
- scope.$watch isWidgetHidden, (isHidden) ->
- scope.layout.hidden = isHidden
-
- # Watch for the widget model to change, indicating this widget needs to be updated
- scope.$watch 'widget', (newValue, oldValue) ->
- widget = newValue
-
- # Ignore widgets without a type
- return if _.isEmpty widget.widget
-
- noscrollClass = if widget.noscroll == true
- ' widget-noscroll'
- else
- ''
-
- # Create the include for the specific widget referenced
- template = ''
-
- if widget.allowFullscreen != false
- template = '' + template
-
- compiledValue = $compile(template)(scope)
-
- # Replace the current contents with the newly compiled element
- element.contents().remove()
- element.append(compiledValue)
-
- return
-
- # Watch for page layout changes and resize the widget
- scope.$watch('layout', (layout, oldLayout) ->
+ # Update the layout
+ updateLayout = (layout) ->
# Ensure a valid layout is provided
if _.isUndefined(layout)
@@ -122,7 +62,7 @@ cyclotronDirectives.directive 'widget', ($compile, $sce, $window, layoutService)
# Copy gridWidth/width into the scope
# Apply overrides if necessary (mobile devices)
if layout.forceGridWidth?
- if widget.gridWidth == layout.originalGridColumns
+ if scope.widget.gridWidth == layout.originalGridColumns
scope.widgetGridWidth = layout.gridColumns
else
scope.widgetGridWidth = layout.forceGridWidth
@@ -169,7 +109,69 @@ cyclotronDirectives.directive 'widget', ($compile, $sce, $window, layoutService)
return
- , true)
+ # Determine if a Widget should be visible or hidden on the dashboard
+ isWidgetHidden = ->
+ return false unless scope.widget?
+
+ if scope.pageOverrides?.widgets?
+ widgetOverrides = scope.pageOverrides.widgets?[scope.widgetIndex]
+
+ # If WidgetOverrides.hidden is set true or false, use its value
+ if widgetOverrides?.hidden?
+ return widgetOverrides.hidden == true
+
+ # Else, default to the widget's "hidden" property
+ return scope.widget.hidden == true
+
+ # Store Widget API for use by Dashboards
+ # Use scope.$evalAsync to ensure it gets digested by Angular
+ if scope.widget.name?
+ $window.Cyclotron.currentPage.widgets[scope.widget.name] = {
+ show: ->
+ scope.$evalAsync ->
+ widgetOverrides = scope.pageOverrides?.widgets?[scope.widgetIndex]
+ widgetOverrides.hidden = false
+ hide: ->
+ scope.$evalAsync ->
+ widgetOverrides = scope.pageOverrides?.widgets?[scope.widgetIndex]
+ widgetOverrides.hidden = true
+ toggleVisibility: ->
+ scope.$evalAsync ->
+ widgetOverrides = scope.pageOverrides?.widgets?[scope.widgetIndex]
+ widgetOverrides.hidden = !widgetOverrides.hidden
+ }
+
+ # Watch for the widget model to change, indicating this widget needs to be updated
+ scope.$watch 'widget', (newValue, oldValue) ->
+ widget = newValue
+
+ # Ignore widgets without a type
+ return if _.isEmpty widget.widget
+
+ noscrollClass = if widget.noscroll == true
+ ' widget-noscroll'
+ else
+ ''
+
+ # Create the include for the specific widget referenced
+ template = ''
+
+ if widget.allowFullscreen != false
+ template = '' + template
+
+ compiledValue = $compile(template)(scope)
+
+ # Replace the current contents with the newly compiled element
+ element.contents().remove()
+ element.append(compiledValue)
+
+ return
+
+ # Watch for page layout changes and resize the widget
+ scope.$watch 'layout', updateLayout, true
+
+ # Watch for widget visibility to change
+ scope.$watch 'pageOverrides', (-> updateLayout(scope.layout)), true
return
}
diff --git a/cyclotron-site/app/scripts/dashboards/directives/directives.widgetBody.coffee b/cyclotron-site/app/scripts/dashboards/directives/directives.widgetBody.coffee
index d3ba1c4..80d2559 100644
--- a/cyclotron-site/app/scripts/dashboards/directives/directives.widgetBody.coffee
+++ b/cyclotron-site/app/scripts/dashboards/directives/directives.widgetBody.coffee
@@ -32,10 +32,10 @@ cyclotronDirectives.directive 'widgetBody', ($timeout) ->
scope.widgetLayout.widgetBodyHeight = widgetBodyHeight
# Update on window resizing
- $widget.add('.title, .widget-footer').on 'resize', _.throttle(->
+ $widget.add('.title, .widget-footer').on 'resize', _.debounce(->
scope.$apply ->
sizer()
- , 65)
+ , 120, { leading: false, maxWait: 500 })
# Run now & again in 100ms
sizer()
diff --git a/cyclotron-site/app/scripts/dashboards/directives/directives.widgetError.coffee b/cyclotron-site/app/scripts/dashboards/directives/directives.widgetError.coffee
index f117629..da045fa 100644
--- a/cyclotron-site/app/scripts/dashboards/directives/directives.widgetError.coffee
+++ b/cyclotron-site/app/scripts/dashboards/directives/directives.widgetError.coffee
@@ -53,7 +53,7 @@ cyclotronDirectives.directive 'widgetError', ($timeout) ->
scope.errorMessage = scope.dataSourceErrorMessage
if not _.isString(scope.errorMessage)
- $scope.errorMessage = JSON.stringify scope.errorMessage
+ scope.errorMessage = JSON.stringify scope.errorMessage
if errorMessageLength < 30
scope.shortErrorMessage = null
@@ -70,7 +70,7 @@ cyclotronDirectives.directive 'widgetError', ($timeout) ->
$widget.add('.title, .widget-footer').on 'resize', _.throttle(->
scope.$apply ->
sizer()
- , 65)
+ , 120, { leading: false, maxWait: 500 })
# Run now & again in 100ms
sizer()
diff --git a/cyclotron-site/app/scripts/dashboards/services/services.dashboardOverridesService.coffee b/cyclotron-site/app/scripts/dashboards/services/services.dashboardOverridesService.coffee
new file mode 100644
index 0000000..83ccaec
--- /dev/null
+++ b/cyclotron-site/app/scripts/dashboards/services/services.dashboardOverridesService.coffee
@@ -0,0 +1,71 @@
+###
+# Copyright (c) 2016 the original author or authors.
+#
+# Licensed under the MIT License (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.opensource.org/licenses/mit-license.php
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+###
+
+#
+# Manages Cyclotron.dashboardOverrides
+#
+cyclotronServices.factory 'dashboardOverridesService', ($localForage, $q, $window, configService, logService) ->
+
+ getLocalStorageKey = (dashboard) ->
+ 'dashboardOverrides.' + dashboard.name
+
+ resetOverrides = ->
+ return { pages: [] }
+
+ expandOverrides = (dashboard, dashboardOverrides) ->
+ dashboardOverrides.pages ?= []
+ _.each dashboard.pages, (page, index) ->
+ if !dashboardOverrides.pages[index]?
+ dashboardOverrides.pages.push { widgets: [] }
+ dashboardOverrides.pages[index].widgets ?= []
+ _.each page.widgets, (widget, widgetIndex) ->
+ if !dashboardOverrides.pages[index].widgets[widgetIndex]?
+ dashboardOverrides.pages[index].widgets.push {}
+
+ return dashboardOverrides
+
+ return {
+
+ # Load Dashboard Overrides for a Dashboard
+ # Returns a promise after overrides have been initialized
+ initializeDashboardOverrides: (dashboard) ->
+ return $q (resolve, reject) ->
+
+ $localForage.getItem(getLocalStorageKey(dashboard)).then (dashboardOverrides) ->
+ if _.isNull dashboardOverrides
+ dashboardOverrides = resetOverrides()
+
+ # Pad out the overrides with empty pages/widgets
+ dashboardOverrides = expandOverrides dashboard, dashboardOverrides
+
+ logService.debug 'Dashboard Overrides: ' + JSON.stringify(dashboardOverrides)
+ resolve dashboardOverrides
+
+ .catch (error) ->
+ logService.error 'Error loading Dashboard Overrides:', error
+ reject error
+
+ resetAndExpandOverrides: (dashboard) ->
+ dashboardOverrides = resetOverrides()
+ expandOverrides dashboard, dashboardOverrides
+
+ saveDashboardOverrides: (dashboard, dashboardOverrides) ->
+ $localForage.setItem(getLocalStorageKey(dashboard), dashboardOverrides).then ->
+ logService.debug 'Saved Dashboard Overrides to localstorage!'
+ .catch (error) ->
+ logService.error 'Error saving Dashboard Overrides:', error
+
+ }
diff --git a/cyclotron-site/app/scripts/mgmt/controller.guiEditor.coffee b/cyclotron-site/app/scripts/mgmt/controller.guiEditor.coffee
index 950efc2..c3ff22f 100644
--- a/cyclotron-site/app/scripts/mgmt/controller.guiEditor.coffee
+++ b/cyclotron-site/app/scripts/mgmt/controller.guiEditor.coffee
@@ -188,15 +188,7 @@ cyclotronApp.controller 'GuiEditorController', ($scope, $state, $stateParams, $l
$scope.getPageName = dashboardService.getPageName
- $scope.getWidgetName = (widget, index) ->
- if !widget.widget? || widget.widget == ''
- return 'Widget ' + (index + 1)
- if widget?.name?.length > 0
- return _.titleCase(widget.widget) + ': ' + widget.name
- else if widget.title?.length > 0
- return _.titleCase(widget.widget) + ': ' + widget.title
- else
- return _.titleCase(widget.widget)
+ $scope.getWidgetName = dashboardService.getWidgetName
$scope.getParameterName = (item, index) ->
if item.name?.length > 0
diff --git a/cyclotron-site/app/scripts/mgmt/directives/directives.metricsGraphics.coffee b/cyclotron-site/app/scripts/mgmt/directives/directives.metricsGraphics.coffee
index ec3eab3..6a32f2e 100644
--- a/cyclotron-site/app/scripts/mgmt/directives/directives.metricsGraphics.coffee
+++ b/cyclotron-site/app/scripts/mgmt/directives/directives.metricsGraphics.coffee
@@ -58,9 +58,9 @@ cyclotronDirectives.directive 'metricsGraphics', ->
scope.$watch 'data', (newData) ->
redraw()
- $element.resize(_.throttle(->
+ $element.resize _.debounce ->
scope.$apply ->
scope.width = $element.width()
redraw()
- , 65))
+ , 90, { leading: false, maxWait: 200 }
}
diff --git a/cyclotron-site/app/styles/common/main.less b/cyclotron-site/app/styles/common/main.less
index e4ed0ed..325ba33 100644
--- a/cyclotron-site/app/styles/common/main.less
+++ b/cyclotron-site/app/styles/common/main.less
@@ -141,6 +141,10 @@ table {
cursor: pointer;
}
+.switch {
+ box-sizing: content-box !important;
+}
+
#browserError {
width: 60%;
max-width: 500px;
diff --git a/cyclotron-site/app/styles/common/variables.less b/cyclotron-site/app/styles/common/variables.less
index 6d4ae94..0c2cff8 100644
--- a/cyclotron-site/app/styles/common/variables.less
+++ b/cyclotron-site/app/styles/common/variables.less
@@ -53,3 +53,38 @@
border-bottom: 1px dotted #888;
}
}
+
+.unlink() {
+ text-decoration: none;
+ cursor: pointer;
+ border-bottom: 0;
+ &:hover {
+ color: unset;
+ border-bottom: 0;
+ }
+}
+
+/* Dashboard Sidebar */
+@sidebar-width: 300px;
+@sidebar-expander-height: 8px;
+@sidebar-expander-width: 8px;
+@sidebar-header-height: 100px;
+@sidebar-footer-height: 67px;
+@sidebar-transition-duration: 0.5s;
+
+@sidebar-outline-color: #ccc;
+@sidebar-expander-color: black;
+@sidebar-header-color: #333333;
+@sidebar-header-background-color: #f0f0f0;
+@sidebar-header-font-size: 1.6rem;
+@sidebar-iconbar-color: #202020;
+@sidebar-iconbar-background-color: #fff;
+@sidebar-heading-font-size: 1.5rem;
+@sidebar-heading-color: #333333;
+@sidebar-heading-background-color: #e0e0e0;
+@sidebar-heading-border-bottom: 1px solid white;
+@sidebar-panel-color: inherit;
+@sidebar-panel-background-color: white;
+@sidebar-footer-color: #333333;
+@sidebar-footer-background-color: #f0f0f0;
+
diff --git a/cyclotron-site/app/styles/dashboards/_sidebar.less b/cyclotron-site/app/styles/dashboards/_sidebar.less
new file mode 100644
index 0000000..4379df5
--- /dev/null
+++ b/cyclotron-site/app/styles/dashboards/_sidebar.less
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2016 the original author or authors.
+ *
+ * Licensed under the MIT License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+&.has-sidebar {
+ /* Add a special margin on the left side of the dashboard for the sidebar */
+ margin-left: @sidebar-expander-width;
+}
+
+.dashboard-sidebar {
+ width: @sidebar-width;
+ border-right: @sidebar-expander-width solid @sidebar-outline-color;
+ transition: margin-left @sidebar-transition-duration, border-right @sidebar-transition-duration;
+
+ &.collapsed {
+ margin-left: -@sidebar-width;
+ }
+
+ .sidebar-expander-hitbox {
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 100%;
+ width: @sidebar-expander-width * 1.5;
+ margin-right: -@sidebar-expander-width * 1.5;
+ z-index: 9001;
+ cursor: pointer;
+ }
+
+ .sidebar-expander {
+ position: absolute;
+ top: 50%;
+ left: 0;
+ margin-top: -@sidebar-expander-height/2;
+ height: @sidebar-expander-height;
+ width: @sidebar-expander-width;
+ color: @sidebar-expander-color;
+ line-height: @sidebar-expander-height;
+ text-align: center;
+ }
+
+ .sidebar-header {
+ display: block;
+ width: 100%;
+ border-bottom: 1px solid @sidebar-outline-color;
+
+ color: @sidebar-header-color;
+ background-color: @sidebar-header-background-color;
+
+ h1 {
+ font-size: @sidebar-header-font-size;
+ margin-bottom: 0.5rem;
+ padding: 1rem;
+ }
+
+ table.iconbar {
+ border-top: 1px solid @sidebar-outline-color;
+ width: 100%;
+ color: @sidebar-iconbar-color;
+ background-color: @sidebar-iconbar-background-color;
+ td {
+ text-align: center;
+ padding: 10px 15px 5px 15px;
+ a {
+ .unlink();
+ color: @sidebar-iconbar-color;
+ }
+ i.fa {
+ font-size: 2rem;
+ }
+ }
+ }
+ }
+
+ .sidebar-accordion {
+ display: block;
+ width: 100%;
+
+ .panel-group {
+ margin-bottom: 0;
+ height: 100%;
+ }
+
+ .panel-body {
+ .box-sizing(border-box);
+ overflow-y: auto;
+ }
+
+ .panel-group .panel-heading+.panel-collapse .panel-body {
+ border-top: 0;
+ }
+
+ .panel {
+ border: 0;
+ color: @sidebar-panel-color;
+ background-color: @sidebar-panel-background-color;
+ }
+
+ .panel-group .panel {
+ margin-bottom: 0;
+ border-radius: 0;
+ }
+
+ .panel-group .panel+.panel {
+ margin-top: 0;
+ }
+
+ .panel-heading {
+ cursor: pointer;
+ padding: 10px 15px;
+ border-bottom: @sidebar-heading-border-bottom;
+ border-top-right-radius: 0;
+ border-top-left-radius: 0;
+ color: @sidebar-heading-color;
+ background-color: @sidebar-heading-background-color;
+
+ .panel-title {
+ font-size: @sidebar-heading-font-size;
+ }
+ }
+ }
+
+ .sidebar-footer {
+ .box-sizing(border-box);
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: @sidebar-footer-height;
+ padding: 1rem;
+ border-top: 1px solid @sidebar-outline-color;
+ color: @sidebar-footer-color;
+ background-color: @sidebar-footer-background-color;
+
+ .logos {
+ .user-select(none);
+ text-align: center;
+
+ a {
+ .unlink();
+ }
+
+ img {
+ height: 32px;
+ vertical-align: top;
+ margin-right: .4rem;
+ cursor: pointer;
+ }
+ }
+ }
+}
diff --git a/cyclotron-site/app/styles/dashboards/dashboard.less b/cyclotron-site/app/styles/dashboards/dashboard.less
index df393af..5bcce34 100644
--- a/cyclotron-site/app/styles/dashboards/dashboard.less
+++ b/cyclotron-site/app/styles/dashboards/dashboard.less
@@ -41,6 +41,23 @@
margin: 0;
}
+ .dashboard-sidebar {
+ /* Basic styles and initial positioning to avoid initial transition */
+ /* Sidebar styling is loaded per theme */
+ * {
+ .box-sizing(border-box);
+ }
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ z-index: 9000;
+
+ &.collapsed {
+ margin-left: -@sidebar-width * 1.2;
+ }
+ }
+
.dashboard-controls {
.user-select(none);
z-index: 999;
@@ -71,6 +88,16 @@
}
}
+ .click-cover {
+ position: fixed;
+ z-index: 8999;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ display: none;
+ }
+
.dashboard-pages {
position: relative;
}
diff --git a/cyclotron-site/app/styles/themes/dark.less b/cyclotron-site/app/styles/themes/dark.less
index 18610f5..144053a 100644
--- a/cyclotron-site/app/styles/themes/dark.less
+++ b/cyclotron-site/app/styles/themes/dark.less
@@ -29,8 +29,32 @@
@headerColor: #202020;
@tableBorderColor: #ccc;
-
/* Dashboard Styles */
+.dashboard.dashboard-dark {
+ background-color: @pageBackgroundColor;
+
+ .dashboard-page-background {
+ background-color: @pageBackgroundColor;
+ }
+
+ @sidebar-outline-color: #666;
+ @sidebar-expander-color: #ddd;
+ @sidebar-header-color: @color;
+ @sidebar-header-background-color: lighten(@pageBackgroundColor, 1%);
+ @sidebar-iconbar-color: lighten(@color, 10%);
+ @sidebar-iconbar-background-color: @widgetBackgroundColor; //lighten(@pageBackgroundColor, 8%);
+ @sidebar-heading-color: @color;
+ @sidebar-heading-background-color: lighten(@pageBackgroundColor, 15%);;
+ @sidebar-heading-border-bottom: 1px solid @widgetBackgroundColor;
+ @sidebar-panel-color: @widgetBackgroundColor;
+ @sidebar-panel-background-color: lighten(@pageBackgroundColor, 70%);
+ @sidebar-footer-color: @color;
+ @sidebar-footer-background-color: lighten(@pageBackgroundColor, 1%);
+
+ /* Dashboard elements */
+ @import "../dashboards/_sidebar.less";
+}
+
.dashboard-page {
/* Well style, normal only */
&.normal {
diff --git a/cyclotron-site/app/styles/themes/dark2.less b/cyclotron-site/app/styles/themes/dark2.less
index 6cab203..69ab395 100644
--- a/cyclotron-site/app/styles/themes/dark2.less
+++ b/cyclotron-site/app/styles/themes/dark2.less
@@ -31,8 +31,32 @@
@headerPadding: 5px;
-
/* Dashboard Styles */
+.dashboard.dashboard-dark2 {
+ background-color: @pageBackgroundColor;
+
+ .dashboard-page-background {
+ background-color: @pageBackgroundColor;
+ }
+
+ @sidebar-outline-color: #666;
+ @sidebar-expander-color: #ddd;
+ @sidebar-header-color: @color;
+ @sidebar-header-background-color: lighten(@pageBackgroundColor, 15%);
+ @sidebar-iconbar-color: lighten(@color, 10%);
+ @sidebar-iconbar-background-color: @widgetBackgroundColor; //lighten(@pageBackgroundColor, 8%);
+ @sidebar-heading-color: @color;
+ @sidebar-heading-background-color: lighten(@pageBackgroundColor, 15%);;
+ @sidebar-heading-border-bottom: 1px solid @widgetBackgroundColor;
+ @sidebar-panel-color: @widgetBackgroundColor;
+ @sidebar-panel-background-color: lighten(@pageBackgroundColor, 70%);
+ @sidebar-footer-color: @color;
+ @sidebar-footer-background-color: lighten(@pageBackgroundColor, 1%);
+
+ /* Dashboard elements */
+ @import "../dashboards/_sidebar.less";
+}
+
.dashboard-page {
&.normal {
.dashboard-dark2.dashboard-widgetwrapper > .dashboard-widget {
diff --git a/cyclotron-site/app/styles/themes/gto.less b/cyclotron-site/app/styles/themes/gto.less
index 2f9fbb5..34dcd57 100644
--- a/cyclotron-site/app/styles/themes/gto.less
+++ b/cyclotron-site/app/styles/themes/gto.less
@@ -20,7 +20,6 @@
@import "../common/variables.less";
/* Variables */
-
@color: black;
@pageBackgroundColor: white;
@widgetBackgroundColor: white;
@@ -31,6 +30,17 @@
@tableBorderColor: #555;
/* Dashboard Styles */
+.dashboard.dashboard-gto {
+ background-color: @pageBackgroundColor;
+
+ .dashboard-page-background {
+ background-color: @pageBackgroundColor;
+ }
+
+ /* Dashboard elements */
+ @import "../dashboards/_sidebar.less";
+}
+
.dashboard-page {
/* Well style, normal only */
&.normal {
diff --git a/cyclotron-site/app/styles/themes/light.less b/cyclotron-site/app/styles/themes/light.less
index 518adc7..43a7add 100644
--- a/cyclotron-site/app/styles/themes/light.less
+++ b/cyclotron-site/app/styles/themes/light.less
@@ -30,6 +30,22 @@
@tableBorderColor: #555;
/* Dashboard Styles */
+.dashboard.dashboard-light {
+ background-color: @pageBackgroundColor;
+
+ .dashboard-page-background {
+ background-color: @pageBackgroundColor;
+ }
+
+ /* Dashboard elements */
+ @import "../dashboards/_sidebar.less";
+
+ .dashboard-sidebar {
+ .rounded(4px);
+ .box-shadow(1px 1px 6px 0 rgba(0, 0, 0, 0.8));
+ }
+}
+
.dashboard-page {
/* Well style, normal only */
&.normal {
@@ -75,6 +91,7 @@
}
.dashboard-light.dashboard-widgetwrapper {
+
/* Import widget styles, using the theme variables */
@import "../../widgets/annotationChart/_annotationChart.less";
@import "../../widgets/chart/_chart.less";
diff --git a/cyclotron-site/app/styles/themes/lightborderless.less b/cyclotron-site/app/styles/themes/lightborderless.less
index 1212aef..5c86cff 100644
--- a/cyclotron-site/app/styles/themes/lightborderless.less
+++ b/cyclotron-site/app/styles/themes/lightborderless.less
@@ -30,6 +30,21 @@
@tableBorderColor: #555;
/* Dashboard Styles */
+.dashboard.dashboard-lightborderless {
+ background-color: @pageBackgroundColor;
+
+ .dashboard-page-background {
+ background-color: @pageBackgroundColor;
+ }
+
+ /* Dashboard elements */
+ @import "../dashboards/_sidebar.less";
+
+ .dashboard-sidebar {
+ .rounded(4px);
+ .box-shadow(1px 1px 6px 0 rgba(0, 0, 0, 0.8));
+ }
+}
.dashboard-page {
/* Well style, normal only */
&.normal {
diff --git a/cyclotron-site/app/widgets/table/_table.less b/cyclotron-site/app/widgets/table/_table.less
index 2e79e98..acab483 100644
--- a/cyclotron-site/app/widgets/table/_table.less
+++ b/cyclotron-site/app/widgets/table/_table.less
@@ -19,7 +19,11 @@
overflow-x: auto;
height: 100%;
- table th, #container {
+ .widget-body {
+ overflow-y: auto;
+ }
+
+ table th {
border-bottom: 1px dashed #999;
}
diff --git a/cyclotron-site/app/widgets/table/directives.tableColumn.coffee b/cyclotron-site/app/widgets/table/directives.tableColumn.coffee
index 06308bd..7073fd3 100644
--- a/cyclotron-site/app/widgets/table/directives.tableColumn.coffee
+++ b/cyclotron-site/app/widgets/table/directives.tableColumn.coffee
@@ -21,7 +21,7 @@ cyclotronDirectives.directive 'tableColumn', ->
border = scope.column.border
if border?
if border.indexOf('left') >= 0
- $(element).css('border-left-width', '1px')
+ $(element).css 'border-left-width', '1px'
if border.indexOf('right') >= 0
- $(element).css('border-right-width', '1px')
+ $(element).css 'border-right-width', '1px'
}
diff --git a/cyclotron-site/app/widgets/table/directives.tableFixedHeader.coffee b/cyclotron-site/app/widgets/table/directives.tableFixedHeader.coffee
index 0709e93..666d330 100644
--- a/cyclotron-site/app/widgets/table/directives.tableFixedHeader.coffee
+++ b/cyclotron-site/app/widgets/table/directives.tableFixedHeader.coffee
@@ -28,8 +28,12 @@ cyclotronDirectives.directive 'tableFixedHeader', ($window, configService) ->
$table = $(element)
$tableHeaders = null
- $parent = $table.parent()
- $container = $('').appendTo($parent)
+ $widgetBody = $table.parents '.widget-body'
+ $container = $('').appendTo $widgetBody
+
+ pos =
+ originalTop: 0
+ originalLeft: $table.position().left
$container.css({
'overflow': 'hidden'
@@ -38,30 +42,32 @@ cyclotronDirectives.directive 'tableFixedHeader', ($window, configService) ->
'left': $table.position().left
}).hide()
- pos =
- originalTop: 0
- originalLeft: $table.position().left
-
+ # Clone table for fixed header
$clonedTable = $table.clone().empty()
$clonedTable.css({
'position': 'relative'
- 'top': '0'
+
}).appendTo($container)
+ # Add fixed layout if there are no column groups
+ if scope.columnGroups.length == 0
+ $clonedTable.css 'table-layout', 'fixed'
+
#
# Handle resize events
#
resize = ->
- $tableHeaders = $table.find('thead')
- $headerRows = $tableHeaders.find('tr')
- pos.originalTop = $parent.offset().top
- pos.originalLeft = $table.position().left
+ $tableHeaders = $table.children 'thead'
+ $headerRows = $tableHeaders.children 'tr'
+ pos.originalTop = $widgetBody.position().top
+ pos.originalLeft = $table.offset().left
- $container.css({
- width: $parent[0].clientWidth
+ $container.css {
+ width: $widgetBody[0].clientWidth
height: $tableHeaders.height()
- })
+ top: pos.originalTop
+ }
$clonedTable
.empty()
@@ -72,23 +78,27 @@ cyclotronDirectives.directive 'tableFixedHeader', ($window, configService) ->
$(this).css('height', height)
$(this).find('th').each (thIndex) ->
originalHeader = $headerRows.eq(index).find('th').eq(thIndex)
- $(this).css('width', originalHeader.width())
+ $(this).css {
+ width: originalHeader.width()
+ }
- $parent.on 'resize', _.throttle(resize, 200, { leading: false, trailing: true })
+ $widgetBody.on 'resize', _.throttle(resize, 250, { leading: false, maxWait: 500 })
scope.$watch 'sortBy+sortedRows', _.throttle(resize, 200, { leading: false, trailing: true })
#
# Handle scroll events
#
- $parent.on 'scroll', ->
- scrollTop = $parent.scrollTop()
+ $widgetBody.on 'scroll', _.debounce(->
+ scrollTop = $widgetBody.scrollTop()
elementTop = $tableHeaders.offset().top
diff = pos.originalTop - elementTop
- if (diff > 0 && scrollTop > diff && scrollTop <= (diff + $table.height() - $tableHeaders.height()))
+ $container.css 'top', pos.originalTop
+
+ if scrollTop > 0
$clonedTable.css({
- 'left': -$parent.scrollLeft()
+ 'left': -$widgetBody.scrollLeft()
})
if not scope.visible
@@ -98,6 +108,7 @@ cyclotronDirectives.directive 'tableFixedHeader', ($window, configService) ->
else
$container.hide()
scope.visible = false
+ , 120, { leading: false, maxWait: 200 })
#
# Cleanup
@@ -107,8 +118,8 @@ cyclotronDirectives.directive 'tableFixedHeader', ($window, configService) ->
$container.remove()
$container = null
- $parent.off 'resize'
- $parent.off 'scroll'
+ $widgetBody.off 'resize'
+ $widgetBody.off 'scroll'
return
return
diff --git a/cyclotron-site/bower.json b/cyclotron-site/bower.json
index fc01db9..1cc1b77 100644
--- a/cyclotron-site/bower.json
+++ b/cyclotron-site/bower.json
@@ -30,6 +30,7 @@
"angular-ui-ace": "0.2.3",
"angular-ui-router": "0.2.14",
"angular-ui-select": "0.12.1",
+ "angular-ui-switch": "~0.1.1",
"bowser": "1.0.0",
"c3": "0.4.10",
"d3": "3.5.6",
@@ -45,7 +46,7 @@
"lodash": "2.4.1",
"masonry": "3.1.5",
"metrics-graphics": "2.6.0",
- "moment": "2.8.4",
+ "moment": "2.13.0",
"ngTranscludeMod": "izhaki/ngTranscludeMod",
"node-uuid": "1.4.3",
"numeraljs": "1.5.3",
@@ -157,5 +158,8 @@
"src/URI.min.js"
]
}
+ },
+ "devDependencies": {
+ "angular-ui-switch": "~0.1.1"
}
}
diff --git a/cyclotron-site/gulpfile.coffee b/cyclotron-site/gulpfile.coffee
index 9f9a4dd..8c07ab4 100644
--- a/cyclotron-site/gulpfile.coffee
+++ b/cyclotron-site/gulpfile.coffee
@@ -70,6 +70,10 @@ ngAnnotateOptions =
remove: true
single_quotes: true
+plumberError = (error) ->
+ console.log(error)
+ this.emit('end')
+
gulp.task 'clean', (done) ->
del(['./_public', './coverage', 'bower_components'], done)
@@ -222,25 +226,25 @@ gulp.task 'styles', ->
lessFilter = filter '**/*.less'
appCommon = gulp.src './app/styles/common/*.less'
- .pipe plumber()
+ .pipe plumber(plumberError)
.pipe less()
.pipe concat 'css/app.common.css'
.pipe gulp.dest './_public'
- appDashboards = gulp.src './app/styles/dashboards/*.less'
- .pipe plumber()
+ appDashboards = gulp.src ['./app/styles/dashboards/*.less', '!./app/styles/dashboards/_*.less']
+ .pipe plumber(plumberError)
.pipe less()
.pipe concat 'css/app.dashboards.css'
.pipe gulp.dest './_public'
appMgmt = gulp.src './app/styles/mgmt/*.less'
- .pipe plumber()
+ .pipe plumber(plumberError)
.pipe less()
.pipe concat 'css/app.mgmt.css'
.pipe gulp.dest './_public'
themes = gulp.src './app/styles/themes/*.less'
- .pipe plumber()
+ .pipe plumber(plumberError)
.pipe less()
.pipe rename {
prefix: 'app.themes.'
diff --git a/cyclotron-site/package.json b/cyclotron-site/package.json
index ee8cfda..15b0423 100644
--- a/cyclotron-site/package.json
+++ b/cyclotron-site/package.json
@@ -1,7 +1,7 @@
{
"name": "cyclotron-site",
"description": "Cyclotron: website",
- "version": "1.30.0",
+ "version": "1.34.0",
"author": "Dave Bauman ",
"license": "MIT",
"private": true,
diff --git a/cyclotron-site/test/unit/mixins-spec.coffee b/cyclotron-site/test/unit/mixins-spec.coffee
index 37ad819..477c9c7 100644
--- a/cyclotron-site/test/unit/mixins-spec.coffee
+++ b/cyclotron-site/test/unit/mixins-spec.coffee
@@ -162,6 +162,9 @@ describe 'Unit: _.numeralformat', ->
it 'should apply format correctly to strings', ->
expect(_.numeralformat('0,0.0', '2002.041')).toBe '2,002.0'
+ it 'should return the string if a non-numeric string is provided', ->
+ expect(_.numeralformat('0,0.0', 'null')).toBe 'null'
+
describe 'Unit: _.ngApply', ->
actual = null
mockScope = {
diff --git a/cyclotron-site/test/unit/mixins-varsub-spec.coffee b/cyclotron-site/test/unit/mixins-varsub-spec.coffee
index 06aafc9..5274115 100644
--- a/cyclotron-site/test/unit/mixins-varsub-spec.coffee
+++ b/cyclotron-site/test/unit/mixins-varsub-spec.coffee
@@ -74,3 +74,6 @@ describe 'Unit: _.varSub', ->
it 'should return an unchanged string if the variable name is not found but there is a format code', ->
expect(_.varSub('#{number|0.0 %}', {})).toBe '#{number|0.0 %}'
+
+ it 'should return an unchanged string if a format string is provided but the value is not a number', ->
+ expect(_.varSub('#{number|0.0 %}', {number: 'null'})).toBe 'null'
diff --git a/cyclotron-svc/package.json b/cyclotron-svc/package.json
index 81a2148..6eef149 100644
--- a/cyclotron-svc/package.json
+++ b/cyclotron-svc/package.json
@@ -1,7 +1,7 @@
{
"name": "cyclotron-svc",
"description": "Cyclotron: REST API",
- "version": "1.30.0",
+ "version": "1.34.0",
"author": "Dave Bauman ",
"license": "MIT",
"private": true,