diff options
Diffstat (limited to 'usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page')
53 files changed, 12633 insertions, 0 deletions
diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/customWidgetSettings.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/customWidgetSettings.js new file mode 100644 index 00000000..6cabe9f2 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/customWidgetSettings.js @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +app + .controller('CustomSettingsDemoCtrl', function($scope, $interval, $window, widgetDefinitions, defaultWidgets, RandomDataModel) { + + + // Add an additional widget with setting overrides + var definitions = [{ + name: 'congfigurable widget', + directive: 'wt-scope-watch', + dataAttrName: 'value', + dataModelType: RandomDataModel, + dataModelOptions: { + limit: 10 + }, + settingsModalOptions: { + partialTemplateUrl: 'template/configurableWidgetModalOptions.html' + }, + onSettingsClose: function (result, widget) { + if (widget.dataModel && widget.dataModel.updateLimit) { + widget.dataModel.updateLimit(result.dataModelOptions.limit); + } + } + }, { + name: 'override modal widget', + directive: 'wt-scope-watch', + dataAttrName: 'value', + dataModelType: RandomDataModel, + settingsModalOptions: { + templateUrl: 'template/WidgetSpecificSettings.html', + controller: 'WidgetSpecificSettingsCtrl', + backdrop: false + }, + onSettingsClose: function(result, widget) { + console.log('Widget-specific settings resolved!'); + jQuery.extend(true, widget, result); + }, + onSettingsDismiss: function(reason, scope) { + console.log('Settings have been dismissed: ', reason); + console.log('Dashboard scope: ', scope); + } + }]; + + var defaultWidgets = [ + { name: 'congfigurable widget' }, + { name: 'override modal widget' } + ]; + + $scope.dashboardOptions = { + widgetButtons: true, + widgetDefinitions: definitions, + defaultWidgets: defaultWidgets, + storage: $window.localStorage, + storageId: 'custom-settings', + + /* + // Overrides default $uibModal options. + // This can also be set on individual + // widget definition objects (see above). + settingsModalOptions: { + // This will completely override the modal template for all widgets. + // You also have the option to add to the default modal template with settingsModalOptions.partialTemplateUrl (see "configurable widget" above) + templateUrl: 'template/customSettingsTemplate.html' + // We could pass a custom controller name here to be used + // with the widget settings dialog, but for this demo we + // will just keep the default. + // + // controller: 'CustomSettingsModalCtrl' + // + // Other options passed to $uibModal.open can be put here, + // eg: + // + // backdrop: false, + // keyboard: false + // + // @see http://angular-ui.github.io/bootstrap/#/modal <-- heads up: routing on their site was broken as of this writing + }, + */ + + // Called when a widget settings dialog is closed + // by the "ok" method (i.e., the promise is resolved + // and not rejected). This can also be set on individual + // widgets (see above). + onSettingsClose: function(result, widget, scope) { + console.log('Settings result: ', result); + console.log('Widget: ', widget); + console.log('Dashboard scope: ', scope); + jQuery.extend(true, widget, result); + }, + + // Called when a widget settings dialog is closed + // by the "cancel" method (i.e., the promise is rejected + // and not resolved). This can also be set on individual + // widgets (see above). + onSettingsDismiss: function(reason, scope) { + console.log('Settings have been dismissed: ', reason); + console.log('Dashboard scope: ', scope); + } + }; + }) + .controller('WidgetSpecificSettingsCtrl', function ($scope, $uibModalInstance, widget) { + // add widget to scope + $scope.widget = widget; + + // set up result object + $scope.result = jQuery.extend(true, {}, widget); + + $scope.ok = function () { + console.log('calling ok from widget-specific settings controller!'); + $uibModalInstance.close($scope.result); + }; + + $scope.cancel = function () { + console.log('calling cancel from widget-specific settings controller!'); + $uibModalInstance.dismiss('cancel'); + }; + }) diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/dataModel.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/dataModel.js new file mode 100644 index 00000000..d9be52ea --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/dataModel.js @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +app + .factory('RandomDataModel', function ($interval, WidgetDataModel) { + function RandomDataModel() { + } + + RandomDataModel.prototype = Object.create(WidgetDataModel.prototype); + RandomDataModel.prototype.constructor = WidgetDataModel; + + angular.extend(RandomDataModel.prototype, { + init: function () { + var dataModelOptions = this.dataModelOptions; + this.limit = (dataModelOptions && dataModelOptions.limit) ? dataModelOptions.limit : 100; + + this.updateScope('-'); + this.startInterval(); + }, + + startInterval: function () { + $interval.cancel(this.intervalPromise); + + this.intervalPromise = $interval(function () { + var value = Math.floor(Math.random() * this.limit); + this.updateScope(value); + }.bind(this), 500); + }, + + updateLimit: function (limit) { + this.dataModelOptions = this.dataModelOptions ? this.dataModelOptions : {}; + this.dataModelOptions.limit = limit; + this.limit = limit; + }, + + destroy: function () { + WidgetDataModel.prototype.destroy.call(this); + $interval.cancel(this.intervalPromise); + } + }); + + return RandomDataModel; + });
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/demo.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/demo.js new file mode 100644 index 00000000..d598f0d0 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/demo.js @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +app +.config(function ($routeProvider) { + $routeProvider + .when('/view', { + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/app/template/view.html', + controller: 'DemoCtrl', + title: 'simple', + description: 'This is the simplest demo.' + }) + .when('/resize', { + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/app/template/view.html', + controller: 'ResizeDemoCtrl', + title: 'resize', + description: 'This demo showcases widget resizing.' + }) + .when('/custom-settings', { + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/app/template/view.html', + controller: 'CustomSettingsDemoCtrl', + title: 'custom widget settings', + description: 'This demo showcases overriding the widget settings dialog/modal ' + + 'for the entire dashboard and for a specific widget. Click on the cog of each ' + + 'widget to see the custom modal. \n"configurable widget" has "limit" option in the modal ' + + 'that controls RandomDataModel.' + }) + .when('/explicit-saving', { + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/app/template/view.html', + controller: 'ExplicitSaveDemoCtrl', + title: 'explicit saving', + description: 'This demo showcases an option to only save the dashboard state '+ + 'explicitly, e.g. by user input. Notice the "all saved" button in the controls ' + + 'updates as you make saveable changes.' + }) + .when('/layouts', { + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/app/template/layouts.html', + controller: 'LayoutsDemoCtrl', + title: 'dashboard layouts', + description: 'This demo showcases the ability to have "dashboard layouts", ' + + 'meaning the ability to have multiple arbitrary configurations of widgets. For more ' + + 'information, take a look at [issue #31](https://github.com/DataTorrent/malhar-angular-dashboard/issues/31)' + }) + .when('/', { + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/app/template/layouts.html', + controller: 'LayoutsDemoExplicitSaveCtrl', + title: 'layouts explicit saving', + description: 'This demo showcases dashboard layouts with explicit saving enabled.' + }) + .otherwise({ + redirectTo: '/' + }); + }) + .controller('NavBarCtrl', function($scope, $route) { + $scope.$route = $route; + }) + .factory('widgetDefinitions', function(RandomDataModel) { + return [ + { + name: 'random', + directive: 'wt-scope-watch', + attrs: { + value: 'randomValue' + } + }, + { + name: 'time', + directive: 'wt-time' + }, + { + name: 'datamodel', + directive: 'wt-scope-watch', + dataAttrName: 'value', + dataModelType: RandomDataModel + }, + { + name: 'resizable', + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/app/template/resizable.html', + attrs: { + class: 'demo-widget-resizable' + } + }, + { + name: 'fluid', + directive: 'wt-fluid', + size: { + width: '50%', + height: '250px' + } + }, + { + name: 'raptor-report-data', + directive: 'raptor-report-data', + size: { + width: '50%', + height: '300px' + } + }, + { + name: 'raptor-report-chart', + directive: 'raptor-report-chart', + size: { + width: '50%', + height: '300px' + } + }, + { + name: 'r-cloud', + directive: 'r-cloud', + size: { + width: '50%', + height: '300px' + } + } + ]; + + }) + .value('defaultWidgets', [ +// { name: 'random' }, +// { name: 'time' }, +// { name: 'datamodel' }, +// { +// name: 'random', +// style: { +// width: '50%', +// minWidth: '39%' +// } +// }, +// { +// name: 'time', +// style: { +// width: '50%' +// } +// } +// {"name":"raptor-report","title":"Spam Source Line Chart","style":{},"size":{"height":"450px","width":"40%"},"attrs":{"value":"randomValue"},"report_id":"3360"} + + ]) + .controller('DemoCtrl', function ($scope, $interval, $window, widgetDefinitions, defaultWidgets) { + + $scope.dashboardOptions = { + widgetButtons: true, + widgetDefinitions: widgetDefinitions, + defaultWidgets: defaultWidgets, + storage: $window.localStorage, + storageId: 'demo_simple' + }; + $scope.randomValue = Math.random(); + $interval(function () { + $scope.randomValue = Math.random(); + }, 500); + + });
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/demo.less b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/demo.less new file mode 100644 index 00000000..91f4cee9 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/demo.less @@ -0,0 +1,24 @@ +body { + margin: 15px; + padding-top: 50px; +} +a { + cursor: pointer; +} +.layout-tabs { + margin-bottom: 10px; +} + +.demo-widget-fluid { + border: 1px solid blue; + height: 100%; +} + +.demo-widget-fluid > div { + border: 1px solid red; + position: relative; + top: 50%; + -webkit-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); +}
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/directives.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/directives.js new file mode 100644 index 00000000..fe3ee7f7 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/directives.js @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +app + .directive('wtTime', function ($interval) { + return { + restrict: 'A', + scope: true, + replace: true, + template: '<div>Time<div class="alert alert-success">{{time}}</div></div>', + link: function (scope) { + function update() { + scope.time = new Date().toLocaleTimeString(); + } + + update(); + + var promise = $interval(update, 500); + + scope.$on('$destroy', function () { + $interval.cancel(promise); + }); + } + }; + }) + .directive('wtScopeWatch', function () { + return { + restrict: 'A', + replace: true, + template: '<div>Value<div class="alert alert-info">{{value}}</div></div>', + scope: { + value: '=value' + } + }; + }) + .directive('wtFluid', function () { + return { + restrict: 'A', + replace: true, + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/app/template/fluid.html', + scope: true, + controller: function ($scope) { + $scope.$on('widgetResized', function (event, size) { + $scope.width = size.width || $scope.width; + $scope.height = size.height || $scope.height; + }); + } + }; + }) + .directive('raptorReportChart', ['widgetDefinitions','defaultWidgets',function (widgetDefinitions, defaultWidgets) { + return { + restrict: 'A', + replace: true, + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/app/template/raptor-report.html', + scope: true, + controller: function ($scope,$http, $rootScope) { +// console.log('================= Raptor Report scope ================='); +// console.log($scope); + $scope.showChart = false; + $scope.url = "report_embedded#/report_run/c_master="+$scope.widget.report_id+ "&refresh=Y&hideGrid=Y&width="+Math.floor($scope.width*13)+"&height=300"; +// $scope.url = "report_embedded#/report_run/c_master="+$scope.widget.report_id+ "&refresh=Y&hideGrid="+$scope.hideGrid+"&width=550&height=300"; + $rootScope.showdataContainer = false; + $rootScope.$watch('showdataContainer', function () { + console.log('change showdataContainer'); + console.log($rootScope.showdataContainer); + $scope.gridOptions = $rootScope.gridOptions; + $scope.gridOptions = $rootScope.gridOptions; + $scope.uiGridRefresh = function(){ + var columnDefsArray = []; + var columnFreezeEndColumn = $scope.widget.reportData.colIdxTobeFreezed; + var doColumnNeedToFreeze = false; + if(columnFreezeEndColumn && columnFreezeEndColumn.length>0) { + doColumnNeedToFreeze = true; + } + $scope.widget.reportData.reportDataColumns.forEach(function(entry) { + var tempColumnDef = { displayName: entry.columnTitle, field: entry.colId, enableSorting: entry.sortable, + sortingAlgorithm: function(a, b) { + return rowSorter.sortAlpha(a.displayValue, b.displayValue); + }, + cellTemplate: '<div class="ui-grid-cell-contents" style="text-align:{{COL_FIELD.alignment}};" title="TOOLTIP"> '+ + ' <div ng-if="!COL_FIELD.drillDownURL || COL_FIELD.drillDownURL==\'\'">{{COL_FIELD.displayValue}}</div>' + + ' <a ng-if="COL_FIELD.drillDownURL && COL_FIELD.drillDownURL!=\'\'" ng-href="{{COL_FIELD.drillDownURL}}&parent___params==={{grid.appScope.currentReportUrlParams}}" >{{COL_FIELD.displayValue}}</a>' + + '</div>'}; + if(entry.columnWidth && entry.columnWidth!='null' && entry.columnWidth!='pxpx' && entry.columnWidth!='nullpx' && entry.columnWidth!='nullpxpx'){ + tempColumnDef['minWidth'] = entry.columnWidth.substring(0, entry.columnWidth.length - 2); + } else { + tempColumnDef['minWidth'] = '100'; + } + if(doColumnNeedToFreeze) { + tempColumnDef['pinnedLeft']= true; + if(columnFreezeEndColumn === entry.colId){ + doColumnNeedToFreeze = false; + } + } + columnDefsArray.push(tempColumnDef); + }); + + $scope.gridOptions.paginationPageSizes= [$scope.widget.reportData.pageSize]; + $scope.gridOptions.paginationPageSize= $scope.widget.reportData.pageSize; + if($scope.widget.reportData.totalRows<14){ + $scope.widget.gridHeight = (widget.reportData.totalRows+5)*30+'px'; + }else{ + $scope.gridHeight = '400px'; + } + $scope.gridOptions.totalItems = $scope.widget.reportData.totalRows; + $scope.gridOptions.columnDefs= columnDefsArray; + $scope.gridOptions.data= $scope.widget.reportData.reportDataRows; + $scope.gridOptions.exporterPdfHeader.text= $scope.widget.reportData.reportName; + }; + $scope.uiGridRefresh(); + + + /*presence.setGlobal({ + u: $rootScope.currentUser, + s: 'on' + })*/ + }) + $scope.$on('widgetResized', function (event, size) { + console.log("===$scope==="); + console.log($scope); + $scope.width = size.width || $scope.width; + $scope.height = size.height || $scope.height; + $scope.url = "report_embedded#/report_run/c_master="+$scope.widget.report_id+ "&refresh=Y&hideGrid=Y&width="+Math.floor($scope.width*13)+"&height=300"; + }); + } + }; + }]) + .directive('raptorReportData', ['widgetDefinitions','defaultWidgets',function (widgetDefinitions, defaultWidgets) { + return { + restrict: 'A', + replace: true, + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/app/template/raptor-report.html', + scope: true, + controller: function ($scope,$http, $rootScope) { +// console.log('================= Raptor Report scope ================='); +// console.log($scope); + $scope.showChart = false; + $scope.url = "report_embedded#/report_run/c_master="+$scope.widget.report_id+ "&refresh=Y&hideChart=Y&width="+Math.floor($scope.width*13)+"&height=300"; +// $scope.url = "report_embedded#/report_run/c_master="+$scope.widget.report_id+ "&refresh=Y&hideGrid="+$scope.hideGrid+"&width=550&height=300"; + $rootScope.showdataContainer = false; + $rootScope.$watch('showdataContainer', function () { + console.log('change showdataContainer'); + console.log($rootScope.showdataContainer); + $scope.gridOptions = $rootScope.gridOptions; + $scope.gridOptions = $rootScope.gridOptions; + $scope.uiGridRefresh = function(){ + var columnDefsArray = []; + var columnFreezeEndColumn = $scope.widget.reportData.colIdxTobeFreezed; + var doColumnNeedToFreeze = false; + if(columnFreezeEndColumn && columnFreezeEndColumn.length>0) { + doColumnNeedToFreeze = true; + } + $scope.widget.reportData.reportDataColumns.forEach(function(entry) { + var tempColumnDef = { displayName: entry.columnTitle, field: entry.colId, enableSorting: entry.sortable, + sortingAlgorithm: function(a, b) { + return rowSorter.sortAlpha(a.displayValue, b.displayValue); + }, + cellTemplate: '<div class="ui-grid-cell-contents" style="text-align:{{COL_FIELD.alignment}};" title="TOOLTIP"> '+ + ' <div ng-if="!COL_FIELD.drillDownURL || COL_FIELD.drillDownURL==\'\'">{{COL_FIELD.displayValue}}</div>' + + ' <a ng-if="COL_FIELD.drillDownURL && COL_FIELD.drillDownURL!=\'\'" ng-href="{{COL_FIELD.drillDownURL}}&parent___params==={{grid.appScope.currentReportUrlParams}}" >{{COL_FIELD.displayValue}}</a>' + + '</div>'}; + if(entry.columnWidth && entry.columnWidth!='null' && entry.columnWidth!='pxpx' && entry.columnWidth!='nullpx' && entry.columnWidth!='nullpxpx'){ + tempColumnDef['minWidth'] = entry.columnWidth.substring(0, entry.columnWidth.length - 2); + } else { + tempColumnDef['minWidth'] = '100'; + } + if(doColumnNeedToFreeze) { + tempColumnDef['pinnedLeft']= true; + if(columnFreezeEndColumn === entry.colId){ + doColumnNeedToFreeze = false; + } + } + columnDefsArray.push(tempColumnDef); + }); + + $scope.gridOptions.paginationPageSizes= [$scope.widget.reportData.pageSize]; + $scope.gridOptions.paginationPageSize= $scope.widget.reportData.pageSize; + if($scope.widget.reportData.totalRows<14){ + $scope.widget.gridHeight = (widget.reportData.totalRows+5)*30+'px'; + }else{ + $scope.gridHeight = '400px'; + } + $scope.gridOptions.totalItems = $scope.widget.reportData.totalRows; + $scope.gridOptions.columnDefs= columnDefsArray; + $scope.gridOptions.data= $scope.widget.reportData.reportDataRows; + $scope.gridOptions.exporterPdfHeader.text= $scope.widget.reportData.reportName; + }; + $scope.uiGridRefresh(); + + + /*presence.setGlobal({ + u: $rootScope.currentUser, + s: 'on' + })*/ + }) + } + }; + }]) + .directive('rCloud', function () { + return { + restrict: 'A', + replace: true, + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/app/template/r-cloud.html', + scope: true, + controller: function ($scope,$http) { + $scope.showChart = false; + $scope.hideGrid = 'true'; + $scope.url = $scope.widget.rcloud_url; + $scope.$on('widgetResized', function (event, size) { + $scope.width = size.width || $scope.width; + $scope.height = size.height || $scope.height; + }); + } + }; + });
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/explicitSave.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/explicitSave.js new file mode 100644 index 00000000..ad959b21 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/explicitSave.js @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +app + .controller('ExplicitSaveDemoCtrl', function ($scope, $interval, $window, widgetDefinitions, defaultWidgets) { + $scope.dashboardOptions = { + widgetButtons: true, + widgetDefinitions: widgetDefinitions, + defaultWidgets: defaultWidgets, + storage: $window.localStorage, + storageId: 'explicitSave', + explicitSave: true + }; + $scope.randomValue = Math.random(); + $interval(function () { + $scope.randomValue = Math.random(); + }, 500); + });
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/index.css b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/index.css new file mode 100644 index 00000000..c05f70c1 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/index.css @@ -0,0 +1,146 @@ +/* #rightContentUiDashboard { + display:inline-block; +} */ + +#rightContentUiDashboard .browsehappy { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; +} +#rightContentUiDashboard .thumbnail { + height: 200px; +} +#rightContentUiDashboard .thumbnail img.pull-right { + width: 50px; +} +#rightContentUiDashboard body { + margin: 15px; + padding-top: 50px; +} +#rightContentUiDashboard a { + cursor: pointer; +} +#rightContentUiDashboard .layout-tabs { + margin-bottom: 10px; +} +#rightContentUiDashboard .demo-widget-fluid { + border: 1px solid blue; + height: 100%; +} +#rightContentUiDashboard .demo-widget-fluid > div { + border: 1px solid red; + position: relative; + top: 50%; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); +} +#rightContentUiDashboard .dashboard-widget-area { + margin: 10px 0 30px; + min-height: 200px; +} +#rightContentUiDashboard .widget-container { + float: left; + display: inline-block; + width: 33%; + padding-bottom: 0em; +} +#rightContentUiDashboard .widget { + margin: 0 1em 0 0; + background-color: white; + border-radius: 5px; + position: relative; + height: 100%; +} +#rightContentUiDashboard .widget-header { + overflow: hidden; + width:100%; /* for overlaying effect start*/ + position: absolute; + z-index: 10; + opacity: 0.8; + top: 1px; + filter: alpha(opacity=80); /* For IE8 and earlier */ + -webkit-transition: width 2s, height 4s; /* Safari */ + transition: width 2s, height 4s; +} +#rightContentUiDashboard .widget-header .label { + display: inline-block; + vertical-align: middle; +} +#rightContentUiDashboard .widget-header .glyphicon { + cursor: pointer; + float: right; + opacity: 0.5; + margin-left: 5px; +} +#rightContentUiDashboard .widget-header .glyphicon:hover { + opacity: 1; +} +#rightContentUiDashboard .widget-header .widget-title { + vertical-align: middle; +} +#rightContentUiDashboard .widget-header form.widget-title { + display: inline; +} +#rightContentUiDashboard .widget-header form.widget-title input.form-control { + width: auto; + display: inline-block; +} +#rightContentUiDashboard .widget-content { + padding:0px; + overflow: hidden; + position: relative; /* for overlaying effect */ +} +#rightContentUiDashboard .widget .widget-ew-resizer { + position: absolute; + width: 5px; + right: -2px; + height: 100%; + top: 0; + cursor: ew-resize; +} +#rightContentUiDashboard .widget .widget-s-resizer { + cursor: ns-resize; + height: 5px; + width: 100%; + bottom: -7px; + left: 0; +} +#rightContentUiDashboard .widget .widget-resizer-marquee { + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.5); + position: absolute; + top: 0; + left: 0; + z-index: 2; +} +#rightContentUiDashboard .remove-layout-icon { + vertical-align: text-top; + cursor: pointer; + opacity: 0.3; +} +#rightContentUiDashboard .remove-layout-icon:hover { + opacity: 1; +} +#rightContentUiDashboard .layout-title { + display: inline-block; +} + +/* +#container { + width: 100px; + height: 100px; + position: relative; +} +#navi, +#infoi { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; +} +#infoi { + z-index: 10; +} + + */
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/index.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/index.js new file mode 100644 index 00000000..b4cfb97d --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/index.js @@ -0,0 +1,3 @@ +'use strict'; + +angular.module('dashboard', ['ui.bootstrap']); diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/index.less b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/index.less new file mode 100644 index 00000000..87955cbe --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/index.less @@ -0,0 +1,16 @@ +.browsehappy { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; +} + +.thumbnail { + height: 200px; + + img.pull-right { + width: 50px; + } +} +// injector +// endinjector diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/index_original.css b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/index_original.css new file mode 100644 index 00000000..d5cebfb0 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/index_original.css @@ -0,0 +1,113 @@ +.browsehappy { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; +} +.thumbnail { + height: 200px; +} +.thumbnail img.pull-right { + width: 50px; +} +body { + margin: 15px; + padding-top: 50px; +} +a { + cursor: pointer; +} +.layout-tabs { + margin-bottom: 10px; +} +.demo-widget-fluid { + border: 1px solid blue; + height: 100%; +} +.demo-widget-fluid > div { + border: 1px solid red; + position: relative; + top: 50%; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); +} +.dashboard-widget-area { + margin: 10px 0 30px; + min-height: 200px; +} +.widget-container { + float: left; + display: inline-block; + width: 33%; + padding-bottom: 1em; +} +.widget { + margin: 0 1em 0 0; + background-color: white; + border: 2px solid #444; + border-radius: 5px; + position: relative; + height: 100%; +} +.widget-header { + overflow: hidden; +} +.widget-header .label { + display: inline-block; + vertical-align: middle; +} +.widget-header .glyphicon { + cursor: pointer; + float: right; + opacity: 0.5; + margin-left: 5px; +} +.widget-header .glyphicon:hover { + opacity: 1; +} +.widget-header .widget-title { + vertical-align: middle; +} +.widget-header form.widget-title { + display: inline; +} +.widget-header form.widget-title input.form-control { + width: auto; + display: inline-block; +} +.widget-content { + overflow: hidden; +} +.widget .widget-ew-resizer { + position: absolute; + width: 5px; + right: -2px; + height: 100%; + top: 0; + cursor: ew-resize; +} +.widget .widget-s-resizer { + cursor: ns-resize; + height: 5px; + width: 100%; + bottom: -7px; + left: 0; +} +.widget .widget-resizer-marquee { + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.5); + position: absolute; + top: 0; + left: 0; + z-index: 2; +} +.remove-layout-icon { + vertical-align: text-top; + cursor: pointer; + opacity: 0.3; +} +.remove-layout-icon:hover { + opacity: 1; +} +.layout-title { + display: inline-block; +} diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/layouts.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/layouts.js new file mode 100644 index 00000000..fda21ab0 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/layouts.js @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +app + .controller('LayoutsDemoCtrl', function($scope, widgetDefinitions, defaultWidgets, LayoutStorage, $interval) { + $scope.layoutOptions = { + storageId: 'demo-layouts', + storage: localStorage, + storageHash: 'fs4df4d51', + widgetDefinitions: widgetDefinitions, + defaultWidgets: defaultWidgets, + lockDefaultLayouts: true, + defaultLayouts: [ + { title: 'Layout 1', active: true , defaultWidgets: defaultWidgets }, + { title: 'Layout 2', active: false, defaultWidgets: defaultWidgets }, + { title: 'Layout 3', active: false, defaultWidgets: defaultWidgets, locked: false } + ] + }; + $scope.randomValue = Math.random(); + $interval(function () { + $scope.randomValue = Math.random(); + }, 500); + + }) + .controller('LayoutsDemoExplicitSaveCtrl', function($scope, widgetDefinitions, defaultWidgets, LayoutStorage, $interval, $http) { + $http.get('raptor.htm?action=report.search.execute').then( + function(result){ + var data = result.data; + var report_id_name = []; + for (var i in data.rows[0]) { + report_id_name.push({index:i, value: data.rows[0][i][1].searchresultField.displayValue, title: data.rows[0][i][2].searchresultField.displayValue}) + } + $scope.reports = report_id_name; + console.log($scope.reports); + $scope.report1 = $scope.reports[1]; + }); + // Can this be removed? + $scope.rcloud_url= "https://tbd.onap.org/mini.html?notebook=c131ea997453e75303588699936d1896"; + $scope.layoutOptions = { + storageId: 'demo-layouts-explicit-save', + storage: localStorage, + storageHash: 'fs4df4d51', + widgetDefinitions: widgetDefinitions, + defaultWidgets: defaultWidgets, + explicitSave: true, + defaultLayouts: [ + { title: 'Layout 1', active: true , defaultWidgets: defaultWidgets }, + { title: 'Layout 2', active: false, defaultWidgets: defaultWidgets }, + { title: 'Layout 3', active: false, defaultWidgets: defaultWidgets } + ] + }; + $scope.randomValue = Math.random(); + $interval(function () { + $scope.randomValue = Math.random(); + }, 500); + + });
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/report_whitelist.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/report_whitelist.js new file mode 100644 index 00000000..f67d5e64 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/report_whitelist.js @@ -0,0 +1,3 @@ +app.config(function ($sceDelegateProvider) { + $sceDelegateProvider.resourceUrlWhitelist(['self','https://tbd.onap.org/**']); +});
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/resize.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/resize.js new file mode 100644 index 00000000..db22e548 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/resize.js @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +app + .controller('ResizeDemoCtrl', function ($scope, $interval, $window, widgetDefinitions, defaultWidgets) { + defaultWidgets = [ + { name: 'fluid' }, + { name: 'resizable' }, + { name: 'random', style: { width: '50%' } }, + { name: 'time', style: { width: '50%' } } + ]; + + $scope.dashboardOptions = { + widgetButtons: true, + widgetDefinitions: widgetDefinitions, + defaultWidgets: defaultWidgets, + storage: $window.localStorage, + storageId: 'demo_resize' + }; + $scope.randomValue = Math.random(); + $interval(function () { + $scope.randomValue = Math.random(); + }, 500); + }) + .controller('ResizableCtrl', function ($scope) { + $scope.$on('widgetResized', function (event, size) { + $scope.width = size.width || $scope.width; + $scope.height = size.height || $scope.height; + }); + });
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/configurableWidgetModalOptions.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/configurableWidgetModalOptions.html new file mode 100644 index 00000000..b12c3ade --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/configurableWidgetModalOptions.html @@ -0,0 +1,6 @@ +<div class="form-group"> + <label class="col-sm-2 control-label">Random Limit</label> + <div class="col-sm-10"> + <input type="text" class="form-control" name="widgetTitle" ng-model="result.dataModelOptions.limit"> + </div> +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/customSettingsTemplate.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/customSettingsTemplate.html new file mode 100644 index 00000000..be13a89b --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/customSettingsTemplate.html @@ -0,0 +1,19 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="cancel()">×</button> + <h3>Custom Settings Dialog for <small>{{widget.title}}</small></h3> +</div> +<div class="modal-body"> + <form name="form" novalidate class="form-horizontal"> + <div class="form-group"> + <label for="widgetTitle" class="col-sm-2 control-label">Title</label> + <div class="col-sm-10"> + <input type="text" class="form-control" name="widgetTitle" ng-model="result.title"> + </div> + </div> + <div ng-if="widget.partialSettingTemplateUrl" ng-include="widget.partialSettingTemplateUrl"></div> + </form> +</div> +<div class="modal-footer"> + <button type="button" class="btn btn-default" ng-click="cancel()">Cancel</button> + <button type="button" class="btn btn-primary" ng-click="ok()">OK</button> +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/fluid.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/fluid.html new file mode 100644 index 00000000..4bb0c94a --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/fluid.html @@ -0,0 +1,8 @@ +<div class="demo-widget-fluid"> + <div> + <p>Widget takes 100% height (blue border).<p> + <p>Resize the widget vertically to see that this text (red border) stays middle aligned.</p> + <p>New width: {{width}}</p> + <p>New height: {{height}}</p> + </div> +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/layouts.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/layouts.html new file mode 100644 index 00000000..8d6854f7 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/layouts.html @@ -0,0 +1 @@ +<div dashboard-layouts="layoutOptions"></div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/r-cloud.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/r-cloud.html new file mode 100644 index 00000000..46cf7b8b --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/r-cloud.html @@ -0,0 +1,8 @@ +<div class="demo-widget-rcloud"> + <div> +<!-- <h1>R Cloud</h1> --> +<!-- <p>{{url}}</p> --> + <iframe src= {{url}} width="100%" height="500" style="border: none; background-color: #f2f2f2; opacity: 1;"> + </iframe> + </div> +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/raptor-report.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/raptor-report.html new file mode 100644 index 00000000..fed32ada --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/raptor-report.html @@ -0,0 +1,29 @@ +<div class="demo-widget-raptor"> + <div ng-show="true"> +<!-- <p>{{url}}</p> --> + <div style="display:block; border: none; height:50px; background-color:#f2f2f2;"></div> + <iframe src= {{url}} + width="100%" height="500" + style="border: none; background-color: #f2f2f2; opacity: 1;"></iframe> + </div> + +<!-- <div ng-show="!showChart"> + <p>{{showChart}}</p> + <iframe id="chartiframe" ng-show="showChart" width="100%" height="450px" style="border: none" scrolling="no"></iframe> + </div> --> + + <div> + <div> + <div id="grid1" ui-grid="gridOptions" ui-grid-pagination ui-grid-pinning ui-grid-resize-columns class="grid" style="height: {{gridHeight}}"> + <div class="no-rows" ng-show="!gridOptions.data.length"> + <div class="msg"> + <span>{{reportData.message}}</span> + </div> + </div> + </div> + </div> + </div> + + + +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/resizable.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/resizable.html new file mode 100644 index 00000000..6abaad81 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/resizable.html @@ -0,0 +1,6 @@ +<div ng-controller="ResizableCtrl"> + <div> + <div>New width: {{width}}</div> + <div>New height: {{height}}</div> + </div> +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/view.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/view.html new file mode 100644 index 00000000..23b72c28 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/view.html @@ -0,0 +1,5 @@ +<div class="row"> + <div class="col-md-12"> + <div dashboard="dashboardOptions" class="dashboard-container"></div> + </div> +</div> diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/widgetSpecificSettings.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/widgetSpecificSettings.html new file mode 100644 index 00000000..3b93a0a9 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/template/widgetSpecificSettings.html @@ -0,0 +1,19 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="cancel()">×</button> + <h3>Custom Settings for a special widget</h3> +</div> +<div class="modal-body"> + <form name="form" novalidate class="form-horizontal"> + <p>I override the entire widget settings modal template. This can be used for maximum customization!</p> + <div class="form-group"> + <label for="widgetTitle" class="col-sm-2 control-label">Title</label> + <div class="col-sm-10"> + <input type="text" class="form-control" name="widgetTitle" ng-model="result.title"> + </div> + </div> + </form> +</div> +<div class="modal-footer"> + <button type="button" class="btn btn-default" ng-click="cancel()">fuhget about it</button> + <button type="button" class="btn btn-primary" ng-click="ok()">hell yea</button> +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/vendor.css b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/vendor.css new file mode 100644 index 00000000..a7e815ed --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/vendor.css @@ -0,0 +1,6658 @@ +/*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +mark { + background: #ff0; + color: #000; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + box-sizing: content-box; + height: 0; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-appearance: textfield; + box-sizing: content-box; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + border: 0; + padding: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + background: transparent !important; + color: #000 !important; + box-shadow: none !important; + text-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('../../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot'); + src: url('../../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../../bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../../bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'), url('../../bower_components/boostrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../../bower_components/boostrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\002a"; +} +.glyphicon-plus:before { + content: "\002b"; +} +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-cd:before { + content: "\e201"; +} +.glyphicon-save-file:before { + content: "\e202"; +} +.glyphicon-open-file:before { + content: "\e203"; +} +.glyphicon-level-up:before { + content: "\e204"; +} +.glyphicon-copy:before { + content: "\e205"; +} +.glyphicon-paste:before { + content: "\e206"; +} +.glyphicon-alert:before { + content: "\e209"; +} +.glyphicon-equalizer:before { + content: "\e210"; +} +.glyphicon-king:before { + content: "\e211"; +} +.glyphicon-queen:before { + content: "\e212"; +} +.glyphicon-pawn:before { + content: "\e213"; +} +.glyphicon-bishop:before { + content: "\e214"; +} +.glyphicon-knight:before { + content: "\e215"; +} +.glyphicon-baby-formula:before { + content: "\e216"; +} +.glyphicon-tent:before { + content: "\26fa"; +} +.glyphicon-blackboard:before { + content: "\e218"; +} +.glyphicon-bed:before { + content: "\e219"; +} +.glyphicon-apple:before { + content: "\f8ff"; +} +.glyphicon-erase:before { + content: "\e221"; +} +.glyphicon-hourglass:before { + content: "\231b"; +} +.glyphicon-lamp:before { + content: "\e223"; +} +.glyphicon-duplicate:before { + content: "\e224"; +} +.glyphicon-piggy-bank:before { + content: "\e225"; +} +.glyphicon-scissors:before { + content: "\e226"; +} +.glyphicon-bitcoin:before { + content: "\e227"; +} +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} +.glyphicon-yen:before { + content: "\00a5"; +} +.glyphicon-jpy:before { + content: "\00a5"; +} +.glyphicon-ruble:before { + content: "\20bd"; +} +.glyphicon-rub:before { + content: "\20bd"; +} +.glyphicon-scale:before { + content: "\e230"; +} +.glyphicon-ice-lolly:before { + content: "\e231"; +} +.glyphicon-ice-lolly-tasted:before { + content: "\e232"; +} +.glyphicon-education:before { + content: "\e233"; +} +.glyphicon-option-horizontal:before { + content: "\e234"; +} +.glyphicon-option-vertical:before { + content: "\e235"; +} +.glyphicon-menu-hamburger:before { + content: "\e236"; +} +.glyphicon-modal-window:before { + content: "\e237"; +} +.glyphicon-oil:before { + content: "\e238"; +} +.glyphicon-grain:before { + content: "\e239"; +} +.glyphicon-sunglasses:before { + content: "\e240"; +} +.glyphicon-text-size:before { + content: "\e241"; +} +.glyphicon-text-color:before { + content: "\e242"; +} +.glyphicon-text-background:before { + content: "\e243"; +} +.glyphicon-object-align-top:before { + content: "\e244"; +} +.glyphicon-object-align-bottom:before { + content: "\e245"; +} +.glyphicon-object-align-horizontal:before { + content: "\e246"; +} +.glyphicon-object-align-left:before { + content: "\e247"; +} +.glyphicon-object-align-vertical:before { + content: "\e248"; +} +.glyphicon-object-align-right:before { + content: "\e249"; +} +.glyphicon-triangle-right:before { + content: "\e250"; +} +.glyphicon-triangle-left:before { + content: "\e251"; +} +.glyphicon-triangle-bottom:before { + content: "\e252"; +} +.glyphicon-triangle-top:before { + content: "\e253"; +} +.glyphicon-console:before { + content: "\e254"; +} +.glyphicon-superscript:before { + content: "\e255"; +} +.glyphicon-subscript:before { + content: "\e256"; +} +.glyphicon-menu-left:before { + content: "\e257"; +} +.glyphicon-menu-right:before { + content: "\e258"; +} +.glyphicon-menu-down:before { + content: "\e259"; +} +.glyphicon-menu-up:before { + content: "\e260"; +} +* { + box-sizing: border-box; +} +*:before, +*:after { + box-sizing: border-box; +} +html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333333; + background-color: #ffffff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #337ab7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + padding: 4px; + line-height: 1.42857143; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + transition: all 0.2s ease-in-out; + display: inline-block; + max-width: 100%; + height: auto; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eeeeee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +[role="button"] { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #777777; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +mark, +.mark { + background-color: #fcf8e3; + padding: .2em; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #777777; +} +.text-primary { + color: #337ab7; +} +a.text-primary:hover, +a.text-primary:focus { + color: #286090; +} +.text-success { + color: #3c763d; +} +a.text-success:hover, +a.text-success:focus { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover, +a.text-info:focus { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover, +a.text-warning:focus { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover, +a.text-danger:focus { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #337ab7; +} +a.bg-primary:hover, +a.bg-primary:focus { + background-color: #286090; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover, +a.bg-success:focus { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover, +a.bg-info:focus { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover, +a.bg-warning:focus { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover, +a.bg-danger:focus { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eeeeee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + list-style: none; + margin-left: -5px; +} +.list-inline > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + clear: left; + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #777777; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eeeeee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777777; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid #eeeeee; + border-left: 0; + text-align: right; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #ffffff; + background-color: #333333; + border-radius: 3px; + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + box-shadow: none; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + word-break: break-all; + word-wrap: break-word; + color: #333333; + background-color: #f5f5f5; + border: 1px solid #cccccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +.row { + margin-left: -15px; + margin-right: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0%; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0%; + } +} +table { + background-color: transparent; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777777; + text-align: left; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #dddddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #dddddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #dddddd; +} +.table .table { + background-color: #ffffff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #dddddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #dddddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover { + background-color: #f5f5f5; +} +table col[class*="col-"] { + position: static; + float: none; + display: table-column; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + float: none; + display: table-cell; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +.table-responsive { + overflow-x: auto; + min-height: 0.01%; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #dddddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + padding: 0; + margin: 0; + border: 0; + min-width: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555555; + background-color: #ffffff; + background-image: none; + border: 1px solid #cccccc; + border-radius: 4px; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); +} +.form-control::-moz-placeholder { + color: #999999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999999; +} +.form-control::-webkit-input-placeholder { + color: #999999; +} +.form-control::-ms-expand { + border: 0; + background-color: transparent; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #eeeeee; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"].form-control, + input[type="time"].form-control, + input[type="datetime-local"].form-control, + input[type="month"].form-control { + line-height: 34px; + } + input[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .input-group-sm input[type="date"], + .input-group-sm input[type="time"], + .input-group-sm input[type="datetime-local"], + .input-group-sm input[type="month"] { + line-height: 30px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .input-group-lg input[type="date"], + .input-group-lg input[type="time"], + .input-group-lg input[type="datetime-local"], + .input-group-lg input[type="month"] { + line-height: 46px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-left: -20px; + margin-top: 4px \9; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.form-control-static { + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0; + min-height: 34px; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-left: 0; + padding-right: 0; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.form-group-sm select.form-control { + height: 30px; + line-height: 30px; +} +.form-group-sm textarea.form-control, +.form-group-sm select[multiple].form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 30px; + min-height: 32px; + padding: 6px 10px; + font-size: 12px; + line-height: 1.5; +} +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.form-group-lg select.form-control { + height: 46px; + line-height: 46px; +} +.form-group-lg textarea.form-control, +.form-group-lg select[multiple].form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 46px; + min-height: 38px; + padding: 11px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #3c763d; +} +.has-success .form-control { + border-color: #3c763d; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-success .form-control:focus { + border-color: #2b542c; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #3c763d; + border-color: #3c763d; + background-color: #dff0d8; +} +.has-success .form-control-feedback { + color: #3c763d; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #8a6d3b; +} +.has-warning .form-control { + border-color: #8a6d3b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-warning .form-control:focus { + border-color: #66512c; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #8a6d3b; + border-color: #8a6d3b; + background-color: #fcf8e3; +} +.has-warning .form-control-feedback { + color: #8a6d3b; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-error .form-control:focus { + border-color: #843534; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + border-color: #a94442; + background-color: #f2dede; +} +.has-error .form-control-feedback { + color: #a94442; +} +.has-feedback label ~ .form-control-feedback { + top: 25px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: 7px; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-left: -15px; + margin-right: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: right; + margin-bottom: 0; + padding-top: 7px; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 11px; + font-size: 18px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + font-size: 12px; + } +} +.btn { + display: inline-block; + margin-bottom: 0; + font-weight: normal; + text-align: center; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + background-image: none; + border: 1px solid transparent; + white-space: nowrap; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + border-radius: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #333333; + text-decoration: none; +} +.btn:active, +.btn.active { + outline: 0; + background-image: none; + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + opacity: 0.65; + filter: alpha(opacity=65); + box-shadow: none; +} +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} +.btn-default { + color: #333333; + background-color: #ffffff; + border-color: #cccccc; +} +.btn-default:focus, +.btn-default.focus { + color: #333333; + background-color: #e6e6e6; + border-color: #8c8c8c; +} +.btn-default:hover { + color: #333333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #333333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + color: #333333; + background-color: #d4d4d4; + border-color: #8c8c8c; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus { + background-color: #ffffff; + border-color: #cccccc; +} +.btn-default .badge { + color: #ffffff; + background-color: #333333; +} +.btn-primary { + color: #ffffff; + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary:focus, +.btn-primary.focus { + color: #ffffff; + background-color: #286090; + border-color: #122b40; +} +.btn-primary:hover { + color: #ffffff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #ffffff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + color: #ffffff; + background-color: #204d74; + border-color: #122b40; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus { + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary .badge { + color: #337ab7; + background-color: #ffffff; +} +.btn-success { + color: #ffffff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:focus, +.btn-success.focus { + color: #ffffff; + background-color: #449d44; + border-color: #255625; +} +.btn-success:hover { + color: #ffffff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #ffffff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active:hover, +.btn-success.active:hover, +.open > .dropdown-toggle.btn-success:hover, +.btn-success:active:focus, +.btn-success.active:focus, +.open > .dropdown-toggle.btn-success:focus, +.btn-success:active.focus, +.btn-success.active.focus, +.open > .dropdown-toggle.btn-success.focus { + color: #ffffff; + background-color: #398439; + border-color: #255625; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #ffffff; +} +.btn-info { + color: #ffffff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:focus, +.btn-info.focus { + color: #ffffff; + background-color: #31b0d5; + border-color: #1b6d85; +} +.btn-info:hover { + color: #ffffff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #ffffff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active:hover, +.btn-info.active:hover, +.open > .dropdown-toggle.btn-info:hover, +.btn-info:active:focus, +.btn-info.active:focus, +.open > .dropdown-toggle.btn-info:focus, +.btn-info:active.focus, +.btn-info.active.focus, +.open > .dropdown-toggle.btn-info.focus { + color: #ffffff; + background-color: #269abc; + border-color: #1b6d85; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #ffffff; +} +.btn-warning { + color: #ffffff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:focus, +.btn-warning.focus { + color: #ffffff; + background-color: #ec971f; + border-color: #985f0d; +} +.btn-warning:hover { + color: #ffffff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #ffffff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active:hover, +.btn-warning.active:hover, +.open > .dropdown-toggle.btn-warning:hover, +.btn-warning:active:focus, +.btn-warning.active:focus, +.open > .dropdown-toggle.btn-warning:focus, +.btn-warning:active.focus, +.btn-warning.active.focus, +.open > .dropdown-toggle.btn-warning.focus { + color: #ffffff; + background-color: #d58512; + border-color: #985f0d; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #ffffff; +} +.btn-danger { + color: #ffffff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:focus, +.btn-danger.focus { + color: #ffffff; + background-color: #c9302c; + border-color: #761c19; +} +.btn-danger:hover { + color: #ffffff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #ffffff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active:hover, +.btn-danger.active:hover, +.open > .dropdown-toggle.btn-danger:hover, +.btn-danger:active:focus, +.btn-danger.active:focus, +.open > .dropdown-toggle.btn-danger:focus, +.btn-danger:active.focus, +.btn-danger.active.focus, +.open > .dropdown-toggle.btn-danger.focus { + color: #ffffff; + background-color: #ac2925; + border-color: #761c19; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #ffffff; +} +.btn-link { + color: #337ab7; + font-weight: normal; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #23527c; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777777; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + transition: opacity 0.15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + transition-property: height, visibility; + transition-duration: 0.35s; + transition-timing-function: ease; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid \9; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + font-size: 14px; + text-align: left; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + text-decoration: none; + color: #262626; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #ffffff; + text-decoration: none; + outline: 0; + background-color: #337ab7; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #777777; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + cursor: not-allowed; +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + left: auto; + right: 0; +} +.dropdown-menu-left { + left: 0; + right: auto; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777777; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + border-top: 0; + border-bottom: 4px dashed; + border-bottom: 4px solid \9; + content: ""; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + left: auto; + right: 0; + } + .navbar-right .dropdown-menu-left { + left: 0; + right: auto; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn, +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} +.btn-group.open .dropdown-toggle { + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} +.btn-group.open .dropdown-toggle.btn-link { + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-right-radius: 0; + border-top-left-radius: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + float: none; + display: table-cell; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-left: 0; + padding-right: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group .form-control:focus { + z-index: 3; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555555; + text-align: center; + background-color: #eeeeee; + border: 1px solid #cccccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + z-index: 2; + margin-left: -1px; +} +.nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} +.nav > li.disabled > a { + color: #777777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777777; + text-decoration: none; + background-color: transparent; + cursor: not-allowed; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eeeeee; + border-color: #337ab7; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #dddddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eeeeee #eeeeee #dddddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555555; + background-color: #ffffff; + border: 1px solid #dddddd; + border-bottom-color: transparent; + cursor: default; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + text-align: center; + margin-bottom: 5px; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #dddddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #dddddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #ffffff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #ffffff; + background-color: #337ab7; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + text-align: center; + margin-bottom: 5px; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #dddddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #dddddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #ffffff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + overflow-x: visible; + padding-right: 15px; + padding-left: 15px; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + -webkit-overflow-scrolling: touch; +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-left: 0; + padding-right: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; + height: 50px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + margin-right: 15px; + padding: 9px 10px; + margin-top: 8px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} +.navbar-form { + margin-left: -15px; + margin-right: -15px; + padding: 10px 15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + margin-top: 8px; + margin-bottom: 8px; +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + border: 0; + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-left: 15px; + margin-right: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -15px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777777; +} +.navbar-default .navbar-nav > li > a { + color: #777777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #cccccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #dddddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #dddddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #888888; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + background-color: #e7e7e7; + color: #555555; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #cccccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #777777; +} +.navbar-default .navbar-link:hover { + color: #333333; +} +.navbar-default .btn-link { + color: #777777; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #cccccc; +} +.navbar-inverse { + background-color: #222222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #9d9d9d; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #ffffff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #ffffff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #ffffff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #ffffff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + background-color: #080808; + color: #ffffff; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #9d9d9d; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #ffffff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #ffffff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444444; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #9d9d9d; +} +.navbar-inverse .navbar-link:hover { + color: #ffffff; +} +.navbar-inverse .btn-link { + color: #9d9d9d; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #ffffff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444444; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + content: "/\00a0"; + padding: 0 5px; + color: #cccccc; +} +.breadcrumb > .active { + color: #777777; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + line-height: 1.42857143; + text-decoration: none; + color: #337ab7; + background-color: #ffffff; + border: 1px solid #dddddd; + margin-left: -1px; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 2; + color: #23527c; + background-color: #eeeeee; + border-color: #dddddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 3; + color: #ffffff; + background-color: #337ab7; + border-color: #337ab7; + cursor: default; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777777; + background-color: #ffffff; + border-color: #dddddd; + cursor: not-allowed; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-bottom-left-radius: 6px; + border-top-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-bottom-right-radius: 6px; + border-top-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + list-style: none; + text-align: center; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eeeeee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #777777; + background-color: #ffffff; + cursor: not-allowed; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #ffffff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +a.label:hover, +a.label:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #777777; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} +.label-primary { + background-color: #337ab7; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #286090; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + color: #ffffff; + line-height: 1; + vertical-align: middle; + white-space: nowrap; + text-align: center; + background-color: #777777; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge, +.btn-group-xs > .btn .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} +.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #337ab7; + background-color: #ffffff; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding-top: 30px; + padding-bottom: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eeeeee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #d5d5d5; +} +.container .jumbotron, +.container-fluid .jumbotron { + border-radius: 6px; + padding-left: 15px; + padding-right: 15px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron, + .container-fluid .jumbotron { + padding-left: 60px; + padding-right: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 4px; + transition: border 0.2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-left: auto; + margin-right: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #337ab7; +} +.thumbnail .caption { + padding: 9px; + color: #333333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + background-color: #dff0d8; + border-color: #d6e9c6; + color: #3c763d; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + background-color: #d9edf7; + border-color: #bce8f1; + color: #31708f; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + background-color: #fcf8e3; + border-color: #faebcc; + color: #8a6d3b; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + background-color: #f2dede; + border-color: #ebccd1; + color: #a94442; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + overflow: hidden; + height: 20px; + margin-bottom: 20px; + background-color: #f5f5f5; + border-radius: 4px; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #ffffff; + text-align: center; + background-color: #337ab7; + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + transition: width 0.6s ease; +} +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media, +.media-body { + zoom: 1; + overflow: hidden; +} +.media-body { + width: 10000px; +} +.media-object { + display: block; +} +.media-object.img-thumbnail { + max-width: none; +} +.media-right, +.media > .pull-right { + padding-left: 10px; +} +.media-left, +.media > .pull-left { + padding-right: 10px; +} +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} +.media-middle { + vertical-align: middle; +} +.media-bottom { + vertical-align: bottom; +} +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + margin-bottom: 20px; + padding-left: 0; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #ffffff; + border: 1px solid #dddddd; +} +.list-group-item:first-child { + border-top-right-radius: 4px; + border-top-left-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +a.list-group-item, +button.list-group-item { + color: #555555; +} +a.list-group-item .list-group-item-heading, +button.list-group-item .list-group-item-heading { + color: #333333; +} +a.list-group-item:hover, +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { + text-decoration: none; + color: #555555; + background-color: #f5f5f5; +} +button.list-group-item { + width: 100%; + text-align: left; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + background-color: #eeeeee; + color: #777777; + cursor: not-allowed; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777777; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #ffffff; + background-color: #337ab7; + border-color: #337ab7; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #c7ddef; +} +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} +a.list-group-item-success, +button.list-group-item-success { + color: #3c763d; +} +a.list-group-item-success .list-group-item-heading, +button.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +button.list-group-item-success.active, +a.list-group-item-success.active:hover, +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info, +button.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading, +button.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +button.list-group-item-info.active, +a.list-group-item-info.active:hover, +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} +a.list-group-item-warning, +button.list-group-item-warning { + color: #8a6d3b; +} +a.list-group-item-warning .list-group-item-heading, +button.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +button.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} +a.list-group-item-danger, +button.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading, +button.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +button.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #ffffff; + border: 1px solid transparent; + border-radius: 4px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #dddddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-left: 15px; + padding-right: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-right-radius: 3px; + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #dddddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + border: 0; + margin-bottom: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid #dddddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #dddddd; +} +.panel-default { + border-color: #dddddd; +} +.panel-default > .panel-heading { + color: #333333; + background-color: #f5f5f5; + border-color: #dddddd; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #dddddd; +} +.panel-default > .panel-heading .badge { + color: #f5f5f5; + background-color: #333333; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #dddddd; +} +.panel-primary { + border-color: #337ab7; +} +.panel-primary > .panel-heading { + color: #ffffff; + background-color: #337ab7; + border-color: #337ab7; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #337ab7; +} +.panel-primary > .panel-heading .badge { + color: #337ab7; + background-color: #ffffff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #337ab7; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-heading .badge { + color: #dff0d8; + background-color: #3c763d; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-heading .badge { + color: #d9edf7; + background-color: #31708f; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-heading .badge { + color: #fcf8e3; + background-color: #8a6d3b; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-heading .badge { + color: #f2dede; + background-color: #a94442; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ebccd1; +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + left: 0; + bottom: 0; + height: 100%; + width: 100%; + border: 0; +} +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000000; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; + filter: alpha(opacity=20); +} +.close:hover, +.close:focus { + color: #000000; + text-decoration: none; + cursor: pointer; + opacity: 0.5; + filter: alpha(opacity=50); +} +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +.modal-open { + overflow: hidden; +} +.modal { + display: none; + overflow: hidden; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transform: translate(0, -25%); + transform: translate(0, -25%); + transition: -webkit-transform 0.3s ease-out; + transition: transform 0.3s ease-out; +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #ffffff; + border: 1px solid #999999; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + background-clip: padding-box; + outline: 0; +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000000; +} +.modal-backdrop.fade { + opacity: 0; + filter: alpha(opacity=0); +} +.modal-backdrop.in { + opacity: 0.5; + filter: alpha(opacity=50); +} +.modal-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 15px; +} +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-left: 5px; + margin-bottom: 0; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + font-size: 12px; + opacity: 0; + filter: alpha(opacity=0); +} +.tooltip.in { + opacity: 0.9; + filter: alpha(opacity=90); +} +.tooltip.top { + margin-top: -3px; + padding: 5px 0; +} +.tooltip.right { + margin-left: 3px; + padding: 0 5px; +} +.tooltip.bottom { + margin-top: 3px; + padding: 5px 0; +} +.tooltip.left { + margin-left: -3px; + padding: 0 5px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #ffffff; + text-align: center; + background-color: #000000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.top-left .tooltip-arrow { + bottom: 0; + right: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + font-size: 14px; + background-color: #ffffff; + background-clip: padding-box; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + margin: 0; + padding: 8px 14px; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + border-width: 10px; + content: ""; +} +.popover.top > .arrow { + left: 50%; + margin-left: -11px; + border-bottom-width: 0; + border-top-color: #999999; + border-top-color: rgba(0, 0, 0, 0.25); + bottom: -11px; +} +.popover.top > .arrow:after { + content: " "; + bottom: 1px; + margin-left: -10px; + border-bottom-width: 0; + border-top-color: #ffffff; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-left-width: 0; + border-right-color: #999999; + border-right-color: rgba(0, 0, 0, 0.25); +} +.popover.right > .arrow:after { + content: " "; + left: 1px; + bottom: -10px; + border-left-width: 0; + border-right-color: #ffffff; +} +.popover.bottom > .arrow { + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999999; + border-bottom-color: rgba(0, 0, 0, 0.25); + top: -11px; +} +.popover.bottom > .arrow:after { + content: " "; + top: 1px; + margin-left: -10px; + border-top-width: 0; + border-bottom-color: #ffffff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999999; + border-left-color: rgba(0, 0, 0, 0.25); +} +.popover.left > .arrow:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: #ffffff; + bottom: -10px; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + overflow: hidden; + width: 100%; +} +.carousel-inner > .item { + display: none; + position: relative; + transition: 0.6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +@media all and (transform-3d), (-webkit-transform-3d) { + .carousel-inner > .item { + transition: -webkit-transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; + } + .carousel-inner > .item.next, + .carousel-inner > .item.active.right { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + left: 0; + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + left: 0; + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + left: 0; + } +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 15%; + opacity: 0.5; + filter: alpha(opacity=50); + font-size: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); + background-color: rgba(0, 0, 0, 0); +} +.carousel-control.left { + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); +} +.carousel-control.right { + left: auto; + right: 0; + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); +} +.carousel-control:hover, +.carousel-control:focus { + outline: 0; + color: #ffffff; + text-decoration: none; + opacity: 0.9; + filter: alpha(opacity=90); +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + margin-top: -10px; + z-index: 5; + display: inline-block; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + line-height: 1; + font-family: serif; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + margin-left: -30%; + padding-left: 0; + list-style: none; + text-align: center; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + border: 1px solid #ffffff; + border-radius: 10px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); +} +.carousel-indicators .active { + margin: 0; + width: 12px; + height: 12px; + background-color: #ffffff; +} +.carousel-caption { + position: absolute; + left: 15%; + right: 15%; + bottom: 20px; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -10px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -10px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -10px; + } + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-header:before, +.modal-header:after, +.modal-footer:before, +.modal-footer:after { + content: " "; + display: table; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-header:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-left: auto; + margin-right: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/vendor.less b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/vendor.less new file mode 100644 index 00000000..2d0f5cc6 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/app/vendor.less @@ -0,0 +1,3 @@ +@import '../../bower_components/bootstrap/less/bootstrap.less'; + +@icon-font-path: '/fonts/'; diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/angular-markdown-directive/markdown.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/angular-markdown-directive/markdown.js new file mode 100644 index 00000000..989a6bec --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/angular-markdown-directive/markdown.js @@ -0,0 +1,36 @@ +/* + * angular-markdown-directive v0.3.1 + * (c) 2013-2014 Brian Ford http://briantford.com + * License: MIT + */ + +'use strict'; + +angular.module('btford.markdown', ['ngSanitize']). + provider('markdownConverter', function () { + var opts = {}; + return { + config: function (newOpts) { + opts = newOpts; + }, + $get: function () { + return new Showdown.converter(opts); + } + }; + }). + directive('btfMarkdown', ['$sanitize', 'markdownConverter', function ($sanitize, markdownConverter) { + return { + restrict: 'AE', + link: function (scope, element, attrs) { + if (attrs.btfMarkdown) { + scope.$watch(attrs.btfMarkdown, function (newVal) { + var html = newVal ? $sanitize(markdownConverter.makeHtml(newVal)) : ''; + element.html(html); + }); + } else { + var html = $sanitize(markdownConverter.makeHtml(element.text())); + element.html(html); + } + } + }; + }]); diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/WidgetSettingsCtrl.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/WidgetSettingsCtrl.js new file mode 100644 index 00000000..4b420a27 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/WidgetSettingsCtrl.js @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .controller('WidgetSettingsCtrl', ['$scope', '$uibModalInstance', 'widget', function ($scope, $uibModalInstance, widget) { + // add widget to scope + $scope.widget = widget; + + // set up result object + $scope.result = jQuery.extend(true, {}, widget); + + $scope.ok = function () { + $uibModalInstance.close($scope.result); + }; + + $scope.cancel = function () { + $uibModalInstance.dismiss('cancel'); + }; + }]);
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/WidgetSettingsRaptorReportCtrl.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/WidgetSettingsRaptorReportCtrl.js new file mode 100644 index 00000000..fd6a0b02 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/WidgetSettingsRaptorReportCtrl.js @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .controller('WidgetSettingsRaptorReportCtrl', ['$http','$scope','$rootScope','$uibModalInstance', 'widget', function ($http,$scope,$rootScope,$uibModalInstance, widget) { + + // add watch function for widget here + // leave ajax call to the dashboard.js + + console.log("============= WidgetSettingsRaptorReportCtrl scope ================="); + console.log($scope); + + var getFormFieldListUrl = "raptor.htm?action=report.run.container&c_master="+widget.report_id + "&refresh=Y" + console.log("============= getFormFieldListUrl ============="); + console.log(getFormFieldListUrl); + $http.get(getFormFieldListUrl).then( + function(res){ + $scope.reportData = res.data; + // add widget to scope + $scope.showFormFieldIds = false; + $scope.formFieldSelectedValues = {}; +}); + + var parseQueryString = function( queryString ) { + var params = {}, queries, temp, i, l; + // Split into key/value pairs + queries = queryString.split("&"); + // Convert the array of strings into an object + for ( i = 0, l = queries.length; i < l; i++ ) { + temp = queries[i].split('='); + //console.log(temp[0]); + //console.log(temp[0] != "refresh"); + if(temp[0] && temp[0] != "refresh") + params[temp[0]] = temp[1]; + } + return params; + }; + + var paginationOptions = { + pageNumber: 1, + pageSize: 5, + sort: null + }; + + $scope.gridOptions = { + paginationPageSizes: [5], + paginationPageSize: 5, + useExternalPagination: true, + columnDefs: [], + data: [], + enableGridMenu: true, + enableSelectAll: true, + exporterMenuPdf: false, + exporterMenuCsv: false, + exporterCsvFilename: 'myFile.csv', + exporterPdfDefaultStyle: {fontSize: 9}, + exporterPdfTableStyle: {margin: [30, 30, 30, 30]}, + exporterPdfTableHeaderStyle: {fontSize: 10, bol$rootScoped: true, italics: true, color: 'red'}, + exporterPdfHeader: { text: "My Header", style: 'headerStyle' }, + exporterPdfFooter: function ( currentPage, pageCount ) { + return { text: currentPage.toString() + ' of ' + pageCount.toString(), style: 'footerStyle' }; + }, + exporterPdfCustomFormatter: function ( docDefinition ) { + docDefinition.styles.headerStyle = { fontSize: 22, bold: true }; + docDefinition.styles.footerStyle = { fontSize: 10, bold: true }; + return docDefinition; + }, + exporterPdfOrientation: 'portrait', + exporterPdfPageSize: 'LETTER', + exporterPdfMaxGridWidth: 500, + exporterCsvLinkElement: angular.element(document.querySelectorAll(".custom-csv-link-location")), + onRegisterApi: function(gridApi) { + $scope.gridApi = gridApi; + gridApi.pagination.on.paginationChanged($scope, function (newPage, pageSize) { + paginationOptions.pageNumber = newPage; + paginationOptions.pageSize = pageSize; + $scope.runReport(); + }); + } + }; + + + + $scope.getFormFieldSelectedValuesAsURL = function(){ + var formFieldsUrl = ''; + $scope.widget.reportData.formFieldList.forEach(function(formField) { + if(formField.fieldType==='LIST_BOX') { + if($scope.formFieldSelectedValues && $scope.formFieldSelectedValues[formField.fieldId] && $scope.formFieldSelectedValues[formField.fieldId].value != '') { + formFieldsUrl = formFieldsUrl+formField.fieldId+'='+$scope.formFieldSelectedValues[formField.fieldId].value+'&'; + } + } else if(formField.fieldType==='LIST_MULTI_SELECT') { + if($scope.formFieldSelectedValues[formField.fieldId].length >0) { + for (var i = 0; i < $scope.formFieldSelectedValues[formField.fieldId].length; i++) { + if($scope.formFieldSelectedValues[formField.fieldId][i].defaultValue){ + formFieldsUrl = formFieldsUrl+formField.fieldId+'='+$scope.formFieldSelectedValues[formField.fieldId][i].value+'&'; + } + } + } + } else if((formField.fieldType === 'text' || formField.fieldType === 'TEXT') && formField.validationType === 'DATE'){ + formFieldsUrl = formFieldsUrl+formField.fieldId+'='+dateFilter($scope.formFieldSelectedValues[formField.fieldId],$scope.dateformat)+'&'; + } else if((formField.fieldType === 'text' || formField.fieldType === 'TEXT') && formField.validationType === 'TIMESTAMP_MIN'){ + formFieldsUrl = formFieldsUrl+formField.fieldId+'='+dateFilter($scope.formFieldSelectedValues[formField.fieldId],$scope.datetimeformat)+'&'; + } else if((formField.fieldType === 'text' || formField.fieldType === 'TEXT') && $scope.formFieldSelectedValues[formField.fieldId] && $scope.formFieldSelectedValues[formField.fieldId] != ''){ + formFieldsUrl = formFieldsUrl+formField.fieldId+'='+$scope.formFieldSelectedValues[formField.fieldId]+'&'; + } + }); + return formFieldsUrl; + + } + + $scope.triggerOtherFormFields = function(){ + console.log("report_run"); + var formFieldsUrl = $scope.getFormFieldSelectedValuesAsURL(); + $http.get('raptor.htm?action=report.formfields.run.container&c_master='+widget.report_id+'&'+formFieldsUrl).then( + function(response){ + $scope.widget.reportData = response.data; + }); + }; + + + $scope.runReport = function(pagination){ + var formFieldsUrl = $scope.getFormFieldSelectedValuesAsURL(); + console.log("pagination"); + if(!pagination) { + console.log("refreshed ..."); + $scope.gridOptions.pageNumber = 1; + $scope.gridOptions.paginationPageSizes= [widget.reportData.pageSize]; + $scope.gridOptions.paginationPageSize= widget.reportData.pageSize; + if(widget.reportData.totalRows<14){ + $scope.gridHeight = (widget.reportData.totalRows+7)*30+'px'; + } else{ + $scope.gridHeight = '400px'; + } + $scope.gridOptions.totalItems = widget.reportData.totalRows; + $scope.gridOptions.data= widget.reportData.reportDataRows; + $scope.gridOptions.exporterPdfHeader.text= widget.reportData.reportName; + } +/* $scope.currentReportUrlParams = 'c_master='+$scope.urlParams.c_master+'&'+formFieldsUrl+'&display_content=Y&r_page='+(paginationOptions.pageNumber-1); + console.log('raptor.htm?action=report.run.container&c_master='+$scope.urlParams.c_master+'&'+formFieldsUrl+'refresh=Y&display_content=Y&r_page='+(paginationOptions.pageNumber-1)); + $http.get('raptor.htm?action=report.run.container&c_master='+$scope.urlParams.c_master+'&'+formFieldsUrl+'refresh=Y&display_content=Y&r_page='+(paginationOptions.pageNumber-1)).then( + */ + $scope.currentReportUrlParams = 'c_master='+ widget.report_id+'&'+formFieldsUrl+'&display_content=Y&r_page='+(paginationOptions.pageNumber-1); + $scope.urlParams = parseQueryString($scope.currentReportUrlParams); + + console.log('raptor.htm?action=report.run.container&c_master='+ widget.report_id +'&'+formFieldsUrl+'refresh=Y&display_content=Y&r_page='+(paginationOptions.pageNumber-1)); + $http.get('raptor.htm?action=report.run.container&c_master='+widget.report_id+'&'+formFieldsUrl+'refresh=Y&display_content=Y&r_page='+(paginationOptions.pageNumber-1)).then( + function(response){ + widget.reportData = response.data; + if(widget.reportData.errormessage) { + document.getElementById('errorDiv').innerHTML = widget.reportData.errormessage; + console.log(document.getElementById('errorDiv').innerHtml); + console.log(widget.reportData.errormessage); + } + if(!pagination) { + if(!$scope.urlParams.hideChart && widget.reportData.chartAvailable && widget.reportData.totalRows>1){ + console.log('raptor.htm?action=chart.run&c_master='+widget.report_id+'&'+formFieldsUrl+'display_content=Y&r_page='+(paginationOptions.pageNumber-1)); + $http.get('raptor.htm?action=chart.run&c_master='+widget.report_id +'&'+formFieldsUrl+'display_content=Y&r_page='+(paginationOptions.pageNumber-1)).then( + function(response) { + console.log(response.data); + $scope.showChart = true; + document.getElementById('chartiframe').contentWindow.document.write(response.data); + document.getElementById('chartiframe').contentWindow.document.close(); + }); + } else { + $scope.showChart = false; + } + } + if($scope.reportData.displayForm && $scope.reportData.formFieldList && $scope.reportData.formFieldList.length>0 && !$scope.urlParams.hideFormFields){ + $scope.showFormFields = true; + } else { + $scope.showFormFields = false; + } + }); + $rootScope.gridOptions = $scope.gridOptions; + $rootScope.gridHeight = $scope.gridHeight; + $rootScope.showdataContainer = true; + }; + + + + + + // set up result object + $scope.result = jQuery.extend(true, {}, widget); + + $scope.ok = function () { + $uibModalInstance.close($scope.result); + }; + + $scope.okay = function () { + console.log("$scope.okay!") + console.log($scope); + $scope.runReport(); +/* $uibModalInstance.close($scope.result);*/ + }; + + $scope.cancel = function () { + $uibModalInstance.dismiss('cancel'); + }; + }]);
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/add-raptor-report-template.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/add-raptor-report-template.html new file mode 100644 index 00000000..00d6c41a --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/add-raptor-report-template.html @@ -0,0 +1,26 @@ +<div> + <div class="modal-header"> + <h3 class="modal-title"> Add a Raptor Report</h3> + </div> + + <div > + <form name="addRaptorReportForm" class="css-form" novalidate> + <div style="width: 100%;"> + <div style="width: 20%; margin-left: 50px;margin-top:28px; float: left; font-size: 15px;">Report Name: </div> + <div style="margin-top:20px;" class="form-field" att-select="raptorReportList" ng-model="selectedRaptorReport" placeholder="Select a Raptor Report" no-filter="true"></div> + + <form action=""> + <input name="radio" type="radio" ng-model="radioValue" att-radio="chart" title="chart" aria-label="chart radio"> chart + <input name="radio" type="radio" ng-model="radioValue" att-radio="data" title="data" aria-label="data radio"> data + </form> + </div> + + <div class="modal-footer"> + <a att-button btn-type="primary" ng-click="ok()">OK</a> + <a att-button btn-type="primary" ng-click="cancel()">Cancel</a> + </div> + + </form> + <br /> + </div> +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/add-rcloud-notebook-template.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/add-rcloud-notebook-template.html new file mode 100644 index 00000000..239497c9 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/add-rcloud-notebook-template.html @@ -0,0 +1,22 @@ +<div> + <div class="modal-header"> + <h3 class="modal-title"> Add a Raptor Report</h3> + </div> + + <div > + <form name="addRaptorReportForm" class="css-form" novalidate> + <div style="width: 100%;"> + <div style="margin-left: 50px;margin-top:28px; font-size: 15px;">RCloud Notebook URL: </div> +<!-- <div style="margin-top:20px;" class="form-field" att-select="raptorReportList" ng-model="selectedRaptorReport" placeholder="Select a Raptor Report" no-filter="true"></div> --> + <textarea style="margin-left: 50px; width:450px; height: 150px;" ng-model="rcloud_url" name="content"> + </textarea> + </div> + <div class="modal-footer"> + <a att-button btn-type="primary" ng-click="ok()">OK</a> + <a att-button btn-type="primary" ng-click="cancel()">Cancel</a> + </div> + + </form> + <br /> + </div> +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/altDashboard.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/altDashboard.html new file mode 100644 index 00000000..189bccea --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/altDashboard.html @@ -0,0 +1,49 @@ +<div> + <div class="btn-toolbar" ng-if="!options.hideToolbar"> + <div class="btn-group" ng-if="!options.widgetButtons"> + <span class="dropdown" on-toggle="toggled(open)"> + <button type="button" class="btn btn-primary dropdown-toggle" ng-disabled="disabled"> + Button dropdown <span class="caret"></span> + </button> + <ul class="dropdown-menu" role="menu"> + <li ng-repeat="widget in widgetDefs"> + <a href="#" ng-click="addWidgetInternal($event, widget);" class="dropdown-toggle">{{widget.name}}</a> + </li> + </ul> + </span> + </div> + + <div class="btn-group" ng-if="options.widgetButtons"> + <button ng-repeat="widget in widgetDefs" + ng-click="addWidgetInternal($event, widget);" type="button" class="btn btn-primary"> + {{widget.name}} + </button> + </div> + + <button class="btn btn-warning" ng-click="resetWidgetsToDefault()">Default Widgets</button> + + <button ng-if="options.storage && options.explicitSave" ng-click="options.saveDashboard()" class="btn btn-success" ng-hide="!options.unsavedChangeCount">{{ !options.unsavedChangeCount ? "Alternative - No Changes" : "Save" }}</button> + + <button ng-click="clear();" ng-hide="!widgets.length" type="button" class="btn btn-info">Clear</button> + </div> + + <div ui-sortable="sortableOptions" ng-model="widgets" class="dashboard-widget-area"> + <div ng-repeat="widget in widgets" ng-style="widget.style" class="widget-container" widget> + <div class="widget panel panel-default"> + <div class="widget-header panel-heading"> + <h3 class="panel-title"> + <span class="widget-title" ng-dblclick="editTitle(widget)" ng-hide="widget.editingTitle">{{widget.title}}</span> + <form action="" class="widget-title" ng-show="widget.editingTitle" ng-submit="saveTitleEdit(widget)"> + <input type="text" ng-model="widget.title" class="form-control"> + </form> + <span class="label label-primary" ng-if="!options.hideWidgetName">{{widget.name}}</span> + <span ng-click="removeWidget(widget);" class="glyphicon glyphicon-remove icon-erase" ng-if="!options.hideWidgetClose"></span> + <span ng-click="openWidgetSettings(widget);" class="glyphicon glyphicon-cog icon-settings" ng-if="!options.hideWidgetSettings"></span> + </h3> + </div> + <div class="panel-body widget-content"></div> + <div class="widget-ew-resizer" ng-mousedown="grabResizer($event)"></div> + </div> + </div> + </div> +</div> diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.html new file mode 100644 index 00000000..e891b565 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.html @@ -0,0 +1,74 @@ + +<div> + <div class="btn-toolbar" ng-if="!options.hideToolbar"> + <div class="btn-group" ng-if="!options.widgetButtons"> + + <span class="dropdown" on-toggle="toggled(open)"> + <!--<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown"> + Button dropdown<span class="caret"></span> + </button>--> + <ul class="dropdown-menu" role="menu"> + <li ng-repeat="widget in widgetDefs"> + <a href="#" ng-click="addWidgetInternal($event, widget);" class="dropdown-toggle"><span class="label label-primary">{{widget.name}}</span></a> + </li> + </ul> + </span> + </div> + + <div class="btn-group" ng-if="options.widgetButtons"> + <button ng-repeat="widget in widgetDefs" + ng-click="addWidgetInternal($event, widget);" type="button" class="btn btn-primary"> + {{widget.name}} + </button> + </div> + +<!-- <div style="float: left" class="form-field" att-select="reports" ng-model="report1" placeholder="Select a Report"></div> + <button class="btn btn-primary" ng-click="addReport(report1)">Add Raptor Report</button> --> + <button class="btn btn-primary" ng-click="popupAddReport()">+ Raptor Report</button> + +<!-- <div style = "float:left"> <input ng-model = "rcloud_url"> </div> + <button class="btn btn-primary" ng-click="addRCloudNotebook(rcloud_url)">Add R Cloud</button> --> + <button class="btn btn-primary" ng-click="popupAddRCloudNotebook()">+ RCloud Notebook</button> + +<!-- <button class="btn btn-warning" ng-click="resetWidgetsToDefault()">Default Widgets</button> --> + + <button ng-if="options.storage && options.explicitSave" ng-click="options.saveDashboard()" class="btn btn-success" ng-disabled="!options.unsavedChangeCount">{{ !options.unsavedChangeCount ? "All Saved" : "Save Changes (" + options.unsavedChangeCount + ")" }}</button> + + <button ng-click="clear();" type="button" class="btn btn-info">Clear</button> + +<!-- <button style="float:right" ng-click="clear();" type="button" class="btn btn-info">Save to Database</button> --> + </div> + + +<!-- + <div id="container"> + <div id="navi">navi</div> + <div id="infoi"> + <img src="https://appharbor.com/assets/images/stackoverflow-logo.png" height="20" width="32" />infoi + </div> +</div> + --> + + <div ui-sortable="sortableOptions" ng-model="widgets" class="dashboard-widget-area"> + <div ng-mouseover="hoverIn()" ng-mouseleave="hoverOut()" ng-repeat="widget in widgets" ng-style="widget.containerStyle" class="widget-container" widget> + <div class="widget panel panel-default"> + <div style="opacity: 0.8; background-color: #E5E5E5; border: #d3d3d3;" ng-show="hoverEdit" class="widget-header panel-heading"> + <img style="float:left; margin-right: 10px" src="static/fusion/images/att_angular_gridster/grips.png"> + <h3 class="panel-title"> + <span class="widget-title" ng-dblclick="editTitle(widget)" ng-hide="widget.editingTitle"></span> + <form action="" class="widget-title" ng-show="widget.editingTitle" ng-submit="saveTitleEdit(widget)"> + <input type="text" ng-model="widget.title" class="form-control"> + </form> + <span class="label label-primary" ng-if="!options.hideWidgetName">{{widget.name}}</span> + <span ng-click="removeWidget(widget);" class="glyphicon glyphicon-remove icon-erase" ng-if="!options.hideWidgetClose"></span> + <span ng-click="openWidgetSettings(widget);" class="glyphicon glyphicon-cog icon-settings" ng-if="!options.hideWidgetSettings"></span> + <span ng-click="widget.contentStyle.display = widget.contentStyle.display === 'none' ? 'block' : 'none'" class="glyphicon" ng-class="{'glyphicon-plus': widget.contentStyle.display === 'none', 'glyphicon-minus': widget.contentStyle.display !== 'none' }"></span> + </h3> + </div> + <div class="panel-body widget-content" ng-style="widget.contentStyle"></div> + <div class="widget-ew-resizer" ng-mousedown="grabResizer($event)"></div> + <div style="background-color:#f2f2f2" ng-if="widget.enableVerticalResize" class="widget-s-resizer" ng-mousedown="grabSouthResizer($event)"></div> + </div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.js new file mode 100644 index 00000000..4062694e --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.js @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ +'use strict'; + +angular.module('ui.dashboard', ['ui.bootstrap', 'ui.sortable']); + +angular.module('ui.dashboard') + + .directive('dashboard', ['$http','WidgetModel', 'WidgetDefCollection', '$uibModal', 'DashboardState', '$log', function ($http, WidgetModel, WidgetDefCollection, $uibModal, DashboardState, $log) { + + return { + restrict: 'A', + templateUrl: function(element, attr) { + return attr.templateUrl ? attr.templateUrl : 'app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.html'; + }, + scope: true, + + controller: ['$scope', '$attrs', function (scope, attrs) { + // default options + var defaults = { + stringifyStorage: true, + hideWidgetSettings: false, + hideWidgetClose: false, + settingsModalOptions: { + // templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/widget-settings-template.html', + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/widget-settings-raptor-report-template.html', + // controller: 'WidgetSettingsCtrl' + controller: 'WidgetSettingsRaptorReportCtrl' + }, + onSettingsClose: function(result, widget) { // NOTE: dashboard scope is also passed as 3rd argument + jQuery.extend(true, widget, result); + }, + onSettingsDismiss: function(reason) { // NOTE: dashboard scope is also passed as 2nd argument + $log.info('widget settings were dismissed. Reason: ', reason); + } + }; + + scope.hoverEdit = false; + + scope.hoverIn = function(){ + this.hoverEdit = true; + }; + + scope.hoverOut = function(){ + this.hoverEdit = false; + }; + + // from dashboard="options" + scope.options = scope.$eval(attrs.dashboard); + + // Deep options + scope.options.settingsModalOptions = scope.options.settingsModalOptions || {}; + _.each(['settingsModalOptions'], function(key) { + // Ensure it exists on scope.options + scope.options[key] = scope.options[key] || {}; + // Set defaults + _.defaults(scope.options[key], defaults[key]); + }); + + // Shallow options + _.defaults(scope.options, defaults); + + // sortable options + var sortableDefaults = { + stop: function () { + scope.saveDashboard(); + }, + handle: '.widget-header', + distance: 5 + }; + scope.sortableOptions = angular.extend({}, sortableDefaults, scope.options.sortableOptions || {}); + + }], + link: function (scope) { + + // Save default widget config for reset + scope.defaultWidgets = scope.options.defaultWidgets; + + scope.widgetDefs = new WidgetDefCollection(scope.options.widgetDefinitions); + var count = 1; + + // Instantiate new instance of dashboard state + scope.dashboardState = new DashboardState( + scope.options.storage, + scope.options.storageId, + scope.options.storageHash, + scope.widgetDefs, + scope.options.stringifyStorage + ); + + /** + * Instantiates a new widget on the dashboard + * @param {Object} widgetToInstantiate The definition object of the widget to be instantiated + */ + scope.addWidget = function (widgetToInstantiate, doNotSave) { + + if (typeof widgetToInstantiate === 'string') { + widgetToInstantiate = { + name: widgetToInstantiate + }; + } + + var defaultWidgetDefinition = scope.widgetDefs.getByName(widgetToInstantiate.name); + if (!defaultWidgetDefinition) { + throw 'Widget ' + widgetToInstantiate.name + ' is not found.'; + } + + // Determine the title for the new widget + var title; + if (!widgetToInstantiate.title && !defaultWidgetDefinition.title) { + widgetToInstantiate.title = 'Widget ' + count++; + } + + // Instantiation + var widget = new WidgetModel(defaultWidgetDefinition, widgetToInstantiate); + + // Add to the widgets array + scope.widgets.push(widget); + if (!doNotSave) { + scope.saveDashboard(); + } + + return widget; + }; + + /** + * Removes a widget instance from the dashboard + * @param {Object} widget The widget instance object (not a definition object) + */ + scope.removeWidget = function (widget) { + scope.widgets.splice(_.indexOf(scope.widgets, widget), 1); + scope.saveDashboard(); + }; + + /** + * Opens a dialog for setting and changing widget properties + * @param {Object} widget The widget instance object + */ + scope.openWidgetSettings = function (widget) { +/* console.log('======= widgets ======='); + console.log(widget); + console.log('widget.report_id'); + console.log(widget.report_id); +*/ + if (widget.directive.includes("raptor-report")) { + var getFormFieldListUrl = "raptor.htm?action=report.run.container&c_master="+widget.report_id + "&refresh=Y"; + $http.get(getFormFieldListUrl).then( + function(res){ + widget.reportData = res.data; + }); + + // Set up $uibModal options + var options = _.defaults( + { scope: scope }, + widget.settingsModalOptions, + scope.options.settingsModalOptions); + +/* console.log('======= options ======='); + console.log(options); +*/ + // Ensure widget is resolved + options.resolve = { + widget: function () { + return widget; + } + }; + + // Create the modal + var modalInstance = $uibModal.open(options); + var onClose = widget.onSettingsClose || scope.options.onSettingsClose; + var onDismiss = widget.onSettingsDismiss || scope.options.onSettingsDismiss; + + // Set resolve and reject callbacks for the result promise + modalInstance.result.then( + function (result) { + + // Call the close callback + onClose(result, widget, scope); + + //AW Persist title change from options editor + scope.$emit('widgetChanged', widget); + }, + function (reason) { + + // Call the dismiss callback + onDismiss(reason, scope); + + } + ); + + } + + }; + + /** + * Remove all widget instances from dashboard + */ + scope.clear = function (doNotSave) { + scope.widgets = []; + if (doNotSave === true) { + return; + } + scope.saveDashboard(); + }; + + /** + * Used for preventing default on click event + * @param {Object} event A click event + * @param {Object} widgetDef A widget definition object + */ + scope.addWidgetInternal = function (event, widgetDef) { +// event.preventDefault(); + scope.addWidget(widgetDef); + }; + + /** + * Add report to dashboard + */ + scope.popupAddReport = function () { + var modalInstance = $uibModal.open({ + animation: scope.animationsEnabled, + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/add-raptor-report-template.html', + size:'sm', + controller: ['$scope', '$uibModalInstance', '$http', function ($scope, $uibModalInstance, $http) { + $scope.radioValue="chart" + $http.get('raptor.htm?action=report.search.execute').then( + function(result){ + var data = result.data; + var report_id_name = []; + for (var i in data.rows[0]) { + report_id_name.push({index:i, value: data.rows[0][i][1].searchresultField.displayValue, title: data.rows[0][i][2].searchresultField.displayValue}) + } + $scope.raptorReportList = report_id_name; + }); + + $scope.ok = function() { + scope.addReport($scope.selectedRaptorReport,$scope.radioValue); + $uibModalInstance.close(); + }; + $scope.cancel = function() { + $uibModalInstance.dismiss(); + }; + }] + }); + modalInstance.result.then(function () { + $scope.$emit('raptorReportWidgetAdded'); + }, function () { + $log.info('Modal dismissed at: ' + new Date()); + }); + }; + + + scope.popupAddRCloudNotebook = function () { + var modalInstance = $uibModal.open({ + animation: scope.animationsEnabled, + templateUrl: 'app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/add-rcloud-notebook-template.html', + size:'sm', + controller: ['$scope', '$uibModalInstance', '$http', function ($scope, $uibModalInstance, $http) { + $scope.rcloud_url = "" + + $scope.ok = function() { + scope.addRCloudNotebook($scope.rcloud_url); + $uibModalInstance.close(); + }; + $scope.cancel = function() { + $uibModalInstance.dismiss(); + }; + }] + }); + modalInstance.result.then(function () { + $scope.$emit('raptorReportWidgetAdded'); + }, function () { + $log.info('Modal dismissed at: ' + new Date()); + }); + }; + + + scope.addReport = function (report1,radioValue) { + scope.report1 =report1 + var raptor_report_type = "raptor-report-chart" + if (radioValue ==='data') { + raptor_report_type = 'raptor-report-data' + } + console.log("report1") + console.log(report1); +// event.preventDefault(); + var newreport = {"title":report1.title,"name":raptor_report_type ,"style":{},"size":{"height":"350px","width":"40%"},"attrs":{"value":"randomValue"},"report_id":report1.value}; + scope.addWidget(newreport, true); + console.log("widgets"); + console.log(scope.widgets); + ++scope.options.unsavedChangeCount; + return false; + }; + + /** + * Add rcloud notebook to dashboard + */ + scope.addRCloudNotebook = function (rcloud_url) { + ++scope.options.unsavedChangeCount; + /* open a new prompt window */ + //event.preventDefault(); + var newreport = {"title":"R-Cloud","name":"r-cloud","style":{},"size":{"height":"450px","width":"40%"},"attrs":{"value":"randomValue"},"rcloud_url":rcloud_url}; +// console.log("newport"); + console.log(newreport) + scope.addWidget(newreport, true); + /* scope.addWidget("raptor-report");*/ + return false; + }; + + /** + * Uses dashboardState service to save state + */ + scope.saveDashboard = function (force) { + if (!scope.options.explicitSave) { + scope.dashboardState.save(scope.widgets); + } else { + if (!angular.isNumber(scope.options.unsavedChangeCount)) { + scope.options.unsavedChangeCount = 0; + } + if (force) { + scope.options.unsavedChangeCount = 0; + scope.dashboardState.save(scope.widgets); + + } else { + ++scope.options.unsavedChangeCount; + } + } + }; + + /** + * Wraps saveDashboard for external use. + */ + scope.externalSaveDashboard = function(force) { + if (angular.isDefined(force)) { + scope.saveDashboard(force); + } else { + scope.saveDashboard(true); + } + }; + + /** + * Clears current dash and instantiates widget definitions + * @param {Array} widgets Array of definition objects + */ + scope.loadWidgets = function (widgets) { + // AW dashboards are continuously saved today (no "save" button). + console.log("widgets") + scope.defaultWidgets = widgets; + widgets = + [ +// {"title":"DEMO Bar Chart","name":"raptor-report-chart","style":{},"size":{"height":"450px","width":"40%"},"attrs":{"value":"randomValue"},"report_id":"2"}, +// {"title":"Pie Chart","name":"raptor-report-data","style":{},"size":{"height":"450px","width":"40%"},"attrs":{"value":"randomValue"},"report_id":"5"}, +// {"title":"Pie Chart","name":"raptor-report-chart","style":{},"size":{"height":"450px","width":"40%"},"attrs":{"value":"randomValue"},"report_id":"5"} + ]; + console.log('widgets: '); + console.log(JSON.stringify(widgets)); + + scope.savedWidgetDefs = widgets; + scope.clear(true); + _.each(widgets, function (widgetDef) { + scope.addWidget(widgetDef, true); + }); + }; + + /** + * Resets widget instances to default config + * @return {[type]} [description] + */ + scope.resetWidgetsToDefault = function () { + scope.loadWidgets(scope.defaultWidgets); + scope.saveDashboard(); + }; + + // Set default widgets array + var savedWidgetDefs = scope.dashboardState.load(); + + // Success handler + function handleStateLoad(saved) { + scope.options.unsavedChangeCount = 0; + if (saved && saved.length) { + scope.loadWidgets(saved); + } else if (scope.defaultWidgets) { + scope.loadWidgets(scope.defaultWidgets); + } else { + scope.clear(true); + } + } + + if (angular.isArray(savedWidgetDefs)) { + handleStateLoad(savedWidgetDefs); + } else if (savedWidgetDefs && angular.isObject(savedWidgetDefs) && angular.isFunction(savedWidgetDefs.then)) { + savedWidgetDefs.then(handleStateLoad, handleStateLoad); + } else { + handleStateLoad(); + } + + // expose functionality externally + // functions are appended to the provided dashboard options + scope.options.addWidget = scope.addWidget; + scope.options.loadWidgets = scope.loadWidgets; + scope.options.saveDashboard = scope.externalSaveDashboard; + scope.options.removeWidget = scope.removeWidget; + scope.options.openWidgetSettings = scope.openWidgetSettings; + scope.options.clear = scope.clear; + scope.options.resetWidgetsToDefault = scope.resetWidgetsToDefault + + // save state + scope.$on('widgetChanged', function (event) { + event.stopPropagation(); + scope.saveDashboard(); + }); + } + }; + }]); diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.less b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.less new file mode 100644 index 00000000..6b5b717f --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.less @@ -0,0 +1,88 @@ +.dashboard-widget-area { + margin: 10px 0 30px; + min-height: 200px; +} + +.widget-container { + float:left; + display: inline-block; + width: 33%; + padding-bottom: 1em; +} + +.widget { + margin: 0 1em 0 0; + background-color: white; + border: 2px solid #444; + border-radius: 5px; + position: relative; + height: 100%; +} +.widget-header { + overflow: hidden; +} +.widget-header .label { + display: inline-block; + vertical-align: middle; +} +.widget-header .glyphicon { + cursor: pointer; + float: right; + opacity: 0.5; + margin-left: 5px; +} +.widget-header .glyphicon:hover { + opacity: 1; +} +.widget-header .widget-title { + vertical-align: middle; +} +.widget-header form.widget-title { + display: inline; +} + +.widget-header form.widget-title input.form-control { + width: auto; + display: inline-block; +} + +.widget-content { + overflow: hidden; +} + +.widget .widget-ew-resizer { + position: absolute; + width: 5px; + right: -2px; + height:100%; + top:0; + cursor: ew-resize; +} + +.widget .widget-s-resizer { + cursor: ns-resize; + height: 5px; + width: 100%; + bottom: -7px; + left: 0; +} + +.widget .widget-resizer-marquee { + box-shadow: inset 0 0 0 1px rgba(0,0,0,0.5); + position: absolute; + top: 0; + left: 0; + z-index: 2; +} + +.remove-layout-icon { + vertical-align: text-top; + cursor: pointer; + opacity: 0.3; +} +.remove-layout-icon:hover { + opacity: 1; +} +.layout-title { + display: inline-block; +}
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.spec.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.spec.js new file mode 100644 index 00000000..453de431 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.spec.js @@ -0,0 +1,878 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +describe('Directive: dashboard', function () { + + var scope, element, childScope, DashboardState, mockModal, modalOptions, $compile, $q, mockLog; + + // mock UI Sortable + beforeEach(function () { + angular.module('ui.sortable', []); + }); + + // load the directive's module + beforeEach(module('ui.dashboard', function($provide) { + mockModal = { + open: function(options) { + modalOptions = options; + } + }; + mockLog = { + info: function() { + + } + }; + $provide.value('$uibModal', mockModal); + $provide.value('$log', mockLog); + })); + + beforeEach(inject(function (_$compile_, $rootScope, _DashboardState_, _$q_) { + // services + scope = $rootScope.$new(); + $compile = _$compile_; + DashboardState = _DashboardState_; + $q = _$q_; + + // options + var widgetDefinitions = [ + { + name: 'wt-one', + template: '<div class="wt-one-value">{{2 + 2}}</div>' + }, + { + name: 'wt-two', + template: '<span class="wt-two-value">{{value}}</span>' + } + ]; + var defaultWidgets = _.clone(widgetDefinitions); + scope.dashboardOptions = { + widgetButtons: true, + widgetDefinitions: widgetDefinitions, + defaultWidgets: defaultWidgets, + sortableOptions: { + testProperty: 'foobar' + } + }; + scope.value = 10; + + // element setup + element = $compile('<div dashboard="dashboardOptions"></div>')(scope); + scope.$digest(); + childScope = element.scope(); + })); + + it('should have toolbar', function () { + var toolbar = element.find('.btn-toolbar'); + expect(toolbar.length).toEqual(1); + }); + + it('should have UI.Sortable directive', function () { + var widgetArea = element.find('.dashboard-widget-area'); + expect(widgetArea.attr('ui-sortable')).toBeDefined(); + }); + + it('should render widgets', function () { + var widgets = element.find('.widget'); + expect(widgets.length).toEqual(2); + }); + + it('should evaluate widget expressions', function () { + var divWidget = element.find('.wt-one-value'); + expect(divWidget.html()).toEqual('4'); + }); + + it('should evaluate scope expressions', function () { + var spanWidget = element.find('.wt-two-value'); + expect(spanWidget.html()).toEqual('10'); + }); + + it('should fill options with defaults', function() { + expect(scope.dashboardOptions.stringifyStorage).toEqual(true); + }); + + it('should not overwrite specified options with defaults', inject(function($compile) { + scope.dashboardOptions.stringifyStorage = false; + element = $compile('<div dashboard="dashboardOptions"></div>')(scope); + $compile(element)(scope); + scope.$digest(); + expect(scope.dashboardOptions.stringifyStorage).toEqual(false); + })); + + it('should be able to use a different dashboard template', inject(function($compile, $templateCache) { + $templateCache.put( + 'myCustomTemplate.html', + '<div>' + + '<div ui-sortable="sortableOptions" ng-model="widgets">' + + '<div ng-repeat="widget in widgets" ng-style="widget.style" class="widget-container custom-widget" widget>' + + '<h3 class="widget-header">' + + '{{widget.title}}' + + '<span ng-click="removeWidget(widget);" class="glyphicon glyphicon-remove icon-erase" ng-if="!options.hideWidgetClose"></span>' + + '<span ng-click="openWidgetSettings(widget);" class="glyphicon glyphicon-cog icon-settings" ng-if="!options.hideWidgetSettings"></span>' + + '</h3>' + + '<div class="widget-content"></div>' + + '<div class="widget-ew-resizer" ng-mousedown="grabResizer($event)"></div>' + + '</div>' + + '</div>' + + '</div>' + ); + var customElement = $compile('<div dashboard="dashboardOptions" template-url="myCustomTemplate.html"></div>')(scope); + scope.$digest(); + expect(customElement.find('.custom-widget').length).toEqual(2); + })); + + it('should set scope.widgets to an empty array if no defaultWidgets are specified', inject(function($compile) { + delete scope.dashboardOptions.defaultWidgets; + var element2 = $compile('<div dashboard="dashboardOptions"></div>')(scope); + scope.$digest(); + var childScope2 = element2.scope(); + expect(childScope2.widgets instanceof Array).toEqual(true); + })); + + it('should set options.unsavedChangeCount to 0 upon load', function() { + expect(scope.dashboardOptions.unsavedChangeCount).toEqual(0); + }); + + it('should not call saveDashboard on load', inject(function($compile) { + spyOn(DashboardState.prototype, 'save'); + var s = scope.$new(); + element = $compile('<div dashboard="dashboardOptions"></div>')(s); + scope.$digest(); + expect(DashboardState.prototype.save).not.toHaveBeenCalled(); + })); + + describe('the sortableOptions', function() { + + it('should exist', function() { + expect(typeof childScope.sortableOptions).toEqual('object'); + }); + + it('should be possible to be extendable from the dashboardOptions', function() { + expect(childScope.sortableOptions.testProperty).toEqual('foobar'); + }) + + it('should have a stop function that calls $scope.saveDashboard', function() { + expect(typeof childScope.sortableOptions.stop).toEqual('function'); + spyOn(childScope, 'saveDashboard'); + childScope.sortableOptions.stop(); + expect(childScope.saveDashboard).toHaveBeenCalled(); + }); + }); + + describe('the addWidget function', function() { + + var widgetCreated, widgetPassed, widgetDefault; + + beforeEach(function() { + childScope.widgets.push = function(w) { + widgetCreated = w; + } + }); + + it('should be a function', function() { + expect(typeof childScope.addWidget).toEqual('function'); + }); + + it('should throw if no default widgetDefinition was found', function() { + spyOn(childScope.widgetDefs, 'getByName').and.returnValue(false); + function fn () { + childScope.addWidget({ name: 'notReal' }); + } + expect(fn).toThrow(); + }); + + it('should look to the passed widgetToInstantiate object for the title before anything else', function() { + spyOn(childScope.widgetDefs, 'getByName').and.returnValue({ title: 'defaultTitle', name: 'A' }); + childScope.addWidget({ title: 'highestPrecedence', name: 'A' }); + expect(widgetCreated.title).toEqual('highestPrecedence'); + }); + + it('should use the defaultWidget\'s title second', function() { + spyOn(childScope.widgetDefs, 'getByName').and.returnValue({ title: 'defaultTitle', name: 'A' }); + childScope.addWidget({ name: 'A' }); + expect(widgetCreated.title).toEqual('defaultTitle'); + }); + + it('should call the saveDashboard method (internal)', function() { + spyOn(childScope.widgetDefs, 'getByName').and.returnValue({ title: 'defaultTitle', name: 'A' }); + spyOn(childScope, 'saveDashboard'); + childScope.addWidget({ name: 'A' }); + expect(childScope.saveDashboard).toHaveBeenCalled(); + }); + + it('should support passing just the widget name as a string', function() { + spyOn(childScope.widgetDefs, 'getByName').and.returnValue({ title: 'defaultTitle', name: 'A' }); + childScope.addWidget('A'); + expect(childScope.widgetDefs.getByName).toHaveBeenCalledWith('A'); + expect(widgetCreated.title).toEqual('defaultTitle'); + }); + + describe('@awashbrook Test Case', function() { + beforeEach(function() { + spyOn(childScope.widgetDefs, 'getByName').and.returnValue(widgetDefault = { + "name": "nvLineChartAlpha", + "directive": "nvd3-line-chart", + "dataAttrName": "data", + "attrs": { + "isArea": true, + "height": 400, + "showXAxis": true, + "showYAxis": true, + "xAxisTickFormat": "xAxisTickFormat()", + "interactive": true, + "useInteractiveGuideline": true, + "tooltips": true, + "showLegend": true, + "noData": "No data for YOU!", + "color": "colorFunction()", + "forcey": "[0,2]" + }, + "dataModelOptions": { + "params": { + "from": "-2h", + "until": "now" + } + }, + "style": { + "width": "400px" + }, + }); + childScope.addWidget(widgetPassed = { + "title": "Andy", + "name": "nvLineChartAlpha", + "style": { + "width": "400px" + }, + "dataModelOptions": { + "params": { + "from": "-1h", + "target": [ + "randomWalk(\"random Andy 1\")", + "randomWalk(\"random walk 2\")", + "randomWalk(\"random walk 3\")" + ] + } + }, + "attrs": { + "height": 400, + "showXAxis": true, + "showYAxis": true, + "xAxisTickFormat": "xAxisTickFormat()", + "interactive": false, + "useInteractiveGuideline": true, + "tooltips": true, + "showLegend": true, + "noData": "No data for YOU!", + "color": "colorFunction()", + "forcey": "[0,2]", + "data": "widgetData" + } + }); + }); + + it('should keep overrides from widgetPassed', function() { + expect(widgetCreated.attrs.interactive).toEqual(widgetPassed.attrs.interactive); + }); + + it('should fill in default attrs', function() { + expect(widgetCreated.attrs.isArea).toEqual(widgetDefault.attrs.isArea); + }); + + it('should override deep options in dataModelOptions', function() { + expect(widgetCreated.dataModelOptions.params.from).toEqual(widgetPassed.dataModelOptions.params.from); + }); + + it('should fill in deep default attrs', function() { + expect(widgetCreated.dataModelOptions.params.until).toEqual(widgetDefault.dataModelOptions.params.until); + }); + }); + + describe('the doNotSave parameter', function() { + + it('should prevent save from being called if set to true', function() { + spyOn(childScope.widgetDefs, 'getByName').and.returnValue({ title: 'defaultTitle', name: 'A' }); + spyOn(childScope, 'saveDashboard'); + childScope.addWidget({ name: 'A' }, true); + expect(childScope.saveDashboard).not.toHaveBeenCalled(); + }); + + }); + + }); + + describe('the removeWidget function', function() { + + it('should be a function', function() { + expect(typeof childScope.removeWidget).toEqual('function'); + }); + + it('should remove the provided widget from childScope.widgets array', function() { + var startingLength = childScope.widgets.length; + var expectedLength = startingLength - 1; + + var widgetToRemove = childScope.widgets[0]; + childScope.removeWidget(widgetToRemove); + + expect(childScope.widgets.length).toEqual(expectedLength); + expect(childScope.widgets.indexOf(widgetToRemove)).toEqual(-1); + }); + + it('should call saveDashboard', function() { + spyOn(childScope, 'saveDashboard'); + var widgetToRemove = childScope.widgets[0]; + childScope.removeWidget(widgetToRemove); + expect(childScope.saveDashboard).toHaveBeenCalled(); + }); + + }); + + describe('the saveDashboard function', function() { + + it('should be attached to the options object after initialization', function() { + expect(typeof scope.dashboardOptions.saveDashboard).toEqual('function'); + expect(scope.dashboardOptions.saveDashboard === childScope.externalSaveDashboard).toEqual(true); + }); + + it('should call scope.dashboardState.save when called internally if explicitSave is falsey', function() { + spyOn(childScope.dashboardState, 'save').and.returnValue(true); + childScope.saveDashboard(); + expect(childScope.dashboardState.save).toHaveBeenCalled(); + }); + + it('should not call scope.dashboardState.save when called internally if explicitSave is truthy', function() { + scope.dashboardOptions.explicitSave = true; + spyOn(childScope.dashboardState, 'save').and.returnValue(true); + childScope.saveDashboard(); + expect(childScope.dashboardState.save).not.toHaveBeenCalled(); + }); + + it('should call scope.dashboardState.save when called externally, no matter what explicitSave value is', function() { + spyOn(childScope.dashboardState, 'save').and.returnValue(true); + + scope.dashboardOptions.explicitSave = false; + scope.dashboardOptions.saveDashboard(); + expect(childScope.dashboardState.save.calls.count()).toEqual(1); + + scope.dashboardOptions.explicitSave = true; + scope.dashboardOptions.saveDashboard(); + expect(childScope.dashboardState.save.calls.count()).toEqual(2); + }); + + it('should keep a count of unsaved changes as unsavedChangeCount', function() { + scope.dashboardOptions.explicitSave = true; + spyOn(childScope.dashboardState, 'save').and.returnValue(true); + childScope.saveDashboard(); + expect(scope.dashboardOptions.unsavedChangeCount).toEqual(1); + childScope.saveDashboard(); + childScope.saveDashboard(); + expect(scope.dashboardOptions.unsavedChangeCount).toEqual(3); + }); + + it('should reset the cound of unsaved changes if a successful force save occurs', function() { + scope.dashboardOptions.explicitSave = true; + spyOn(childScope.dashboardState, 'save').and.returnValue(true); + + childScope.saveDashboard(); + childScope.saveDashboard(); + childScope.saveDashboard(); + + childScope.saveDashboard(true); + + expect(scope.dashboardOptions.unsavedChangeCount).toEqual(0); + }); + + }); + + describe('the loadWidgets function', function() { + + it('should be a function', function() { + expect(typeof childScope.loadWidgets).toEqual('function'); + }); + + it('should set savedWidgetDefs on scope as passed array', function() { + var widgets = []; + childScope.loadWidgets(widgets); + expect(childScope.savedWidgetDefs === widgets).toEqual(true); + }); + + it('should call clear on the scope with true as the only argument', function() { + spyOn(childScope, 'clear'); + childScope.loadWidgets([]); + expect(childScope.clear).toHaveBeenCalled(); + expect(childScope.clear.calls.argsFor(0)).toEqual([true]); + }); + + it('should call addWidget for each widget in the array', function() { + spyOn(childScope, 'addWidget').and.returnValue(null); + var widgets = [{},{},{}]; + childScope.loadWidgets(widgets); + expect(childScope.addWidget.calls.count()).toEqual(3); + }); + + it('should call addWidget for each widget with true as the second parameter (doNotSave)', function() { + spyOn(childScope, 'addWidget').and.returnValue(null); + var widgets = [{},{},{}]; + childScope.loadWidgets(widgets); + expect(childScope.addWidget.calls.argsFor(0)).toEqual( [ widgets[0], true] ); + expect(childScope.addWidget.calls.argsFor(1)).toEqual( [ widgets[1], true] ); + expect(childScope.addWidget.calls.argsFor(2)).toEqual( [ widgets[2], true] ); + }); + + }); + + describe('the clear function', function() { + + it('should set the scope to an empty array', function() { + childScope.clear(); + expect(childScope.widgets).toEqual([]); + }); + + it('should not call saveDashboard if first arg is true', function() { + spyOn(childScope, 'saveDashboard'); + childScope.clear(true); + expect(childScope.saveDashboard).not.toHaveBeenCalled(); + }); + + it('should call saveDashboard if first arg is not true', function() { + spyOn(childScope, 'saveDashboard'); + childScope.clear(); + expect(childScope.saveDashboard).toHaveBeenCalled(); + }); + + }); + + describe('the openWidgetSettings function', function() { + + it('should be a function', function() { + expect(typeof childScope.openWidgetSettings).toEqual('function'); + }); + + it('should call $uibModal.open with default options', function() { + var widget = {}; + spyOn(mockModal, 'open').and.returnValue({ + result: { then: function(fn) {} } + }); + childScope.openWidgetSettings(widget); + expect(mockModal.open).toHaveBeenCalled(); + }); + + it('should have widget in the resolve object', function() { + var widget = {}; + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + childScope.openWidgetSettings(widget); + expect(modalOptions.resolve.widget() === widget).toEqual(true); + }); + + it('should set the templateUrl in modal options to the default ("app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/widget-settings-template.html")', function() { + var widget = {}; + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + childScope.openWidgetSettings(widget); + expect(modalOptions.templateUrl).toEqual('app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/widget-settings-template.html'); + }); + + it('should set the templateUrl in modal options to scope.options.settingsModalOptions.templateUrl', function() { + var other; + scope.dashboardOptions.settingsModalOptions = { + templateUrl: other = 'some/other/url.html' + }; + var widget = {}; + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + childScope.openWidgetSettings(widget); + expect(modalOptions.templateUrl).toEqual(other); + }); + + it('should set the templateUrl in modal options to widget.settingsModalOptions.templateUrl, if present', function() { + var expected; + var widget = { + settingsModalOptions: { + templateUrl: expected = 'specific/template.html' + } + }; + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + childScope.openWidgetSettings(widget); + expect(modalOptions.templateUrl).toEqual(expected); + }); + + it('should set the controller in modal options to the default ("WidgetSettingsCtrl")', function() { + var widget = {}; + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + childScope.openWidgetSettings(widget); + expect(modalOptions.controller).toEqual('WidgetSettingsCtrl'); + }); + + it('should set the controller in modal options to the default ("WidgetSettingsCtrl"), even when settingsModalOptions is supplied in options', inject(function($rootScope) { + + scope = $rootScope.$new(); + + // options + var widgetDefinitions = [ + { + name: 'wt-one', + template: '<div class="wt-one-value">{{2 + 2}}</div>' + }, + { + name: 'wt-two', + template: '<span class="wt-two-value">{{value}}</span>' + } + ]; + var defaultWidgets = _.clone(widgetDefinitions); + scope.dashboardOptions = { + widgetButtons: true, + widgetDefinitions: widgetDefinitions, + defaultWidgets: defaultWidgets, + sortableOptions: { + testProperty: 'foobar' + }, + settingsModalOptions: { + backdrop: false + } + }; + scope.value = 10; + + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + + // element setup + element = $compile('<div dashboard="dashboardOptions"></div>')(scope); + scope.$digest(); + childScope = element.scope(); + + childScope.openWidgetSettings({}); + expect(modalOptions.controller).toEqual('WidgetSettingsCtrl'); + + })); + + it('should set the controller in modal options to the default ("WidgetSettingsCtrl"), even when settingsModalOptions is supplied in widget', inject(function($rootScope) { + + scope = $rootScope.$new(); + + // options + var widgetDefinitions = [ + { + name: 'wt-one', + template: '<div class="wt-one-value">{{2 + 2}}</div>' + }, + { + name: 'wt-two', + template: '<span class="wt-two-value">{{value}}</span>' + } + ]; + var defaultWidgets = _.clone(widgetDefinitions); + scope.dashboardOptions = { + widgetButtons: true, + widgetDefinitions: widgetDefinitions, + defaultWidgets: defaultWidgets, + sortableOptions: { + testProperty: 'foobar' + }, + settingsModalOptions: { + backdrop: false + } + }; + scope.value = 10; + + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + + // element setup + element = $compile('<div dashboard="dashboardOptions"></div>')(scope); + scope.$digest(); + childScope = element.scope(); + + childScope.openWidgetSettings({ + settingsModalOptions: { + templateUrl: 'custom/widget/template.html' + } + }); + expect(modalOptions.controller).toEqual('WidgetSettingsCtrl'); + expect(modalOptions.backdrop).toEqual(false); + expect(modalOptions.templateUrl).toEqual('custom/widget/template.html'); + + })); + + it('should set the controller to scope.options.settingsModalOptions.controller if provided', function() { + scope.dashboardOptions.settingsModalOptions = {}; + var expected = scope.dashboardOptions.settingsModalOptions.controller = 'MyCustomCtrl'; + var widget = {}; + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + childScope.openWidgetSettings(widget); + expect(modalOptions.controller).toEqual(expected); + }); + + it('should set the controller to widget.settingsModalOptions.controller if provided', function() { + var expected; + var widget = { + settingsModalOptions: { + controller: expected = 'MyWidgetCtrl' + } + }; + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + childScope.openWidgetSettings(widget); + expect(modalOptions.controller).toEqual(expected); + }); + + it('should pass in other modal options set in scope.options.settingsModalOptions', function() { + scope.dashboardOptions.settingsModalOptions = { + keyboard: false, + windowClass: 'my-extra-class' + }; + var widget = {}; + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + childScope.openWidgetSettings(widget); + expect(modalOptions.keyboard).toEqual(false); + expect(modalOptions.windowClass).toEqual('my-extra-class'); + }); + + it('should pass in other modal options set in widget.settingsModalOptions', function() { + scope.dashboardOptions.settingsModalOptions = { + keyboard: false, + windowClass: 'my-extra-class' + }; + var widget = { + settingsModalOptions: { + keyboard: true, + size: 'sm' + } + }; + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + childScope.openWidgetSettings(widget); + expect(modalOptions.keyboard).toEqual(true); + expect(modalOptions.size).toEqual('sm'); + expect(modalOptions.windowClass).toEqual('my-extra-class'); + }); + + it('should emit a "widgetChanged" event on the childScope when the modal promise is called', function(done) { + var widget = {}; + var result = {}; + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + spyOn(childScope.options, 'onSettingsClose'); + childScope.openWidgetSettings(widget); + childScope.$on('widgetChanged', done); + dfr.resolve(result, widget); + childScope.$digest(); + }); + + it('should call scope.options.onSettingsClose when the modal promise is resolved by default', function() { + var widget = {}; + var result = {}; + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + spyOn(childScope.options, 'onSettingsClose'); + childScope.openWidgetSettings(widget); + dfr.resolve(result); + childScope.$digest(); + expect(scope.dashboardOptions.onSettingsClose).toHaveBeenCalledWith(result, widget, childScope); + }); + + it('should call scope.options.onSettingsDismiss when the modal promise is rejected by default', function() { + var widget = {}; + var result = {}; + var dfr = $q.defer(); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + spyOn(childScope.options, 'onSettingsDismiss'); + childScope.openWidgetSettings(widget); + dfr.reject('Testing failure'); + childScope.$digest(); + expect(scope.dashboardOptions.onSettingsDismiss).toHaveBeenCalledWith('Testing failure', childScope); + }); + + it('should call widget.onSettingsClose if provided when the modal promise is resolved', function() { + var widget = { + onSettingsClose: function(result, widget, scope) { + + } + }; + var result = {}; + var dfr = $q.defer(); + spyOn(widget, 'onSettingsClose'); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + spyOn(childScope.options, 'onSettingsClose'); + childScope.openWidgetSettings(widget); + dfr.resolve(result); + childScope.$digest(); + expect(scope.dashboardOptions.onSettingsClose).not.toHaveBeenCalled(); + expect(widget.onSettingsClose).toHaveBeenCalledWith(result, widget, childScope); + }); + + it('should call widget.onSettingsDismiss if provided when the modal promise is rejected', function() { + var widget = { + onSettingsDismiss: function(result, widget, scope) { + + } + }; + var result = {}; + var dfr = $q.defer(); + spyOn(widget, 'onSettingsDismiss'); + spyOn(mockModal, 'open').and.callFake(function(options) { + modalOptions = options; + return { + result: dfr.promise + }; + }); + spyOn(childScope.options, 'onSettingsDismiss'); + childScope.openWidgetSettings(widget); + dfr.reject('Testing failure'); + childScope.$digest(); + expect(scope.dashboardOptions.onSettingsDismiss).not.toHaveBeenCalled(); + expect(widget.onSettingsDismiss).toHaveBeenCalledWith('Testing failure', childScope); + }); + + }); + + describe('the default onSettingsClose callback', function() { + + var onSettingsClose; + + beforeEach(function() { + onSettingsClose = childScope.options.onSettingsClose; + }); + + it('should exist', function() { + expect(typeof onSettingsClose).toEqual('function'); + }); + + it('should deep extend widget with result', function() { + var result = { + title: 'andy', + style: { + 'float': 'left' + } + }; + var widget = { + title: 'scott', + style: { + width: '100px' + } + }; + onSettingsClose(result, widget, {}); + expect(widget).toEqual({ + title: 'andy', + style: { + width: '100px', + 'float': 'left' + } + }); + }); + + }); + + describe('the default onSettingsDismiss callback', function() { + + var onSettingsDismiss; + + beforeEach(function() { + onSettingsDismiss = childScope.options.onSettingsDismiss; + }); + + it('should exist', function() { + expect(typeof onSettingsDismiss).toEqual('function'); + }); + + it('should call $log.info with the reason', function() { + spyOn(mockLog, 'info'); + onSettingsDismiss('dismiss reason'); + expect(mockLog.info).toHaveBeenCalled(); + }); + + }); + +}); diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/widget-settings-raptor-report-template.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/widget-settings-raptor-report-template.html new file mode 100644 index 00000000..7a533c3f --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/widget-settings-raptor-report-template.html @@ -0,0 +1,58 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="cancel()">×</button> + <h3>Widget Options <small>{{widget.title}}</small></h3> +</div> + +<div class="modal-body"> + <form name="form" novalidate class="form-horizontal"> +<!-- <div class="form-group"> + <label for="widgetTitle" class="col-sm-2 control-label">Title</label> + <div style="margin-left:120px" class="col-sm-10"> + <input type="text" class="form-control" name="widgetTitle" ng-model="result.title"> + </div> + </div> --> + +<!-- <div>{{showFormFieldIds}}</div> +<div>{{formFieldSelectedValues}}</div> +<div>{{JSON.strigify(widget.reportData.formFieldList)}}</div> + --> +<form ng-show="true" class="row section-row" style="margin: 10px"> + <form-builder ng-form-fields="reportData.formFieldList" ng-show-field-id="showFormFieldIds" ng-num-form-cols="reportData.numFormCols" ng-model="formFieldSelectedValues" ng-trigger-method="triggerOtherFormFields"></form-builder> +</form> + +<!-- <div ng-repeat="formfield in widget.reportData.formFieldList"> + <div class="form-group"> + <label for="widgetTitle" class="col-sm-2 control-label">{{formfield.fieldDisplayName}}:</label> + <div style="margin-left:120px" class="col-sm-10"> + <input type="text" class="form-control" name="widgetTitle" ng-model="formfield.title"> + </div> + </div> + </div> --> +<!-- <div ng-repeat="(formfield_key,formfield_value) in widget.reportData.formFieldList[0]" class="form-group"> + <label for="widgetTitle" class="col-sm-2 control-label">{{formfield_key}}</label> + <div style="margin-left:120px" class="col-sm-10"> + <input type="text" class="form-control" name="widgetTitle" ng-model="formfield_value"> + </div> + </div> --> + + + <div ng-if="widget.settingsModalOptions.partialTemplateUrl" + ng-include="widget.settingsModalOptions.partialTemplateUrl"></div> + +<!-- + <div ng-show="true" id="grid1" ui-grid="gridOptions" ui-grid-pagination ui-grid-pinning ui-grid-resize-columns class="grid" style="height: {{gridHeight}}"> + <div class="no-rows" ng-show="!gridOptions.data.length"> + <div class="msg"> + <span>{{widget.reportData.message}}</span> + </div> + </div> + </div> + --> + </form> + +</div> + +<div class="modal-footer"> + <button type="button" class="btn btn-default" ng-click="cancel()">Cancel</button> + <button type="button" class="btn btn-primary" ng-click="ok()">OK</button> +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/widget-settings-template.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/widget-settings-template.html new file mode 100644 index 00000000..a57d4366 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/widget-settings-template.html @@ -0,0 +1,22 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="cancel()">×</button> + <h3>Widget Options <small>{{widget.title}}</small></h3> +</div> + +<div class="modal-body"> + <form name="form" novalidate class="form-horizontal"> + <div class="form-group"> + <label for="widgetTitle" class="col-sm-2 control-label">Title</label> + <div class="col-sm-10"> + <input type="text" class="form-control" name="widgetTitle" ng-model="result.title"> + </div> + </div> + <div ng-if="widget.settingsModalOptions.partialTemplateUrl" + ng-include="widget.settingsModalOptions.partialTemplateUrl"></div> + </form> +</div> + +<div class="modal-footer"> + <button type="button" class="btn btn-default" ng-click="cancel()">Cancel</button> + <button type="button" class="btn btn-primary" ng-click="ok()">OK</button> +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModal.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModal.html new file mode 100644 index 00000000..f9f6f361 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModal.html @@ -0,0 +1,13 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="cancel()">×</button> + <h3>Unsaved Changes to "{{layout.title}}"</h3> +</div> + +<div class="modal-body"> + <p>You have {{layout.dashboard.unsavedChangeCount}} unsaved changes on this dashboard. Would you like to save them?</p> +</div> + +<div class="modal-footer"> + <button type="button" class="btn btn-default" ng-click="cancel()">Don't Save</button> + <button type="button" class="btn btn-primary" ng-click="ok()">Save</button> +</div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModalCtrl.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModalCtrl.js new file mode 100644 index 00000000..252f9df4 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModalCtrl.js @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .controller('SaveChangesModalCtrl', ['$scope', '$uibModalInstance', 'layout', function ($scope, $uibModalInstance, layout) { + + // add layout to scope + $scope.layout = layout; + + $scope.ok = function () { + $uibModalInstance.close(); + }; + + $scope.cancel = function () { + $uibModalInstance.dismiss(); + }; + }]);
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.html new file mode 100644 index 00000000..54aef297 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.html @@ -0,0 +1,19 @@ +<ul ui-sortable="sortableOptions" ng-model="layouts" class="nav nav-tabs layout-tabs"> + <li ng-repeat="layout in layouts" ng-class="{ active: layout.active }"> + <a ng-click="makeLayoutActive(layout)"> + <span ng-dblclick="editTitle(layout)" ng-show="!layout.editingTitle">{{layout.title}}</span> + <form action="" class="layout-title" ng-show="layout.editingTitle" ng-submit="saveTitleEdit(layout)"> + <input type="text" ng-model="layout.title" class="form-control" data-layout="{{layout.id}}"> + </form> + <span ng-if="!layout.locked" ng-click="removeLayout(layout)" class="glyphicon glyphicon-remove icon-erase remove-layout-icon"></span> + <!-- <span class="glyphicon glyphicon-pencil"></span> --> + <!-- <span class="glyphicon glyphicon-remove"></span> --> + </a> + </li> + <li> + <a ng-click="createNewLayout()"> + <span class="glyphicon glyphicon-plus"></span> + </a> + </li> +</ul> +<div ng-repeat="layout in layouts | filter:isActive" dashboard="layout.dashboard" template-url="app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.html"></div>
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.js new file mode 100644 index 00000000..bbf107a8 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.js @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .directive('dashboardLayouts', ['LayoutStorage', '$timeout', '$uibModal', + function(LayoutStorage, $timeout, $uibModal) { + return { + scope: true, + templateUrl: function(element, attr) { + return attr.templateUrl ? attr.templateUrl : 'app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.html'; + }, + link: function(scope, element, attrs) { + + scope.options = scope.$eval(attrs.dashboardLayouts); + + var layoutStorage = new LayoutStorage(scope.options); + + scope.layouts = layoutStorage.layouts; + + scope.createNewLayout = function() { + var newLayout = { + title: 'Custom', + defaultWidgets: scope.options.defaultWidgets || [] + }; + layoutStorage.add(newLayout); + scope.makeLayoutActive(newLayout); + layoutStorage.save(); + return newLayout; + }; + + scope.removeLayout = function(layout) { + layoutStorage.remove(layout); + layoutStorage.save(); + }; + + scope.makeLayoutActive = function(layout) { + + var current = layoutStorage.getActiveLayout(); + + if (current && current.dashboard.unsavedChangeCount) { + var modalInstance = $uibModal.open({ + templateUrl: 'template/SaveChangesModal.html', + resolve: { + layout: function() { + return layout; + } + }, + controller: 'SaveChangesModalCtrl' + }); + + // Set resolve and reject callbacks for the result promise + modalInstance.result.then( + function() { + current.dashboard.saveDashboard(); + scope._makeLayoutActive(layout); + }, + function() { + scope._makeLayoutActive(layout); + } + ); + } else { + scope._makeLayoutActive(layout); + } + + }; + + scope._makeLayoutActive = function(layout) { + angular.forEach(scope.layouts, function(l) { + if (l !== layout) { + l.active = false; + } else { + l.active = true; + } + }); + layoutStorage.save(); + }; + + scope.isActive = function(layout) { + return !!layout.active; + }; + + scope.editTitle = function(layout) { + if (layout.locked) { + return; + } + + var input = element.find('input[data-layout="' + layout.id + '"]'); + layout.editingTitle = true; + + $timeout(function() { + input.focus()[0].setSelectionRange(0, 9999); + }); + }; + + // saves whatever is in the title input as the new title + scope.saveTitleEdit = function(layout) { + layout.editingTitle = false; + layoutStorage.save(); + }; + + scope.options.saveLayouts = function() { + layoutStorage.save(true); + }; + scope.options.addWidget = function() { + var layout = layoutStorage.getActiveLayout(); + if (layout) { + layout.dashboard.addWidget.apply(layout.dashboard, arguments); + } + }; + scope.options.loadWidgets = function() { + var layout = layoutStorage.getActiveLayout(); + if (layout) { + layout.dashboard.loadWidgets.apply(layout.dashboard, arguments); + } + }; + scope.options.saveDashboard = function() { + console.log("================= saveDashboard called =================") + var layout = layoutStorage.getActiveLayout(); + console.log("===================== layout ==========================="); + console.log(layout); + if (layout) { + layout.dashboard.saveDashboard.apply(layout.dashboard, arguments); + } + }; + + var sortableDefaults = { + stop: function() { + scope.options.saveLayouts(); + }, + distance: 5 + }; + scope.sortableOptions = angular.extend({}, sortableDefaults, scope.options.sortableOptions || {}); + } + }; + } + ]);
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.spec.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.spec.js new file mode 100644 index 00000000..8533a211 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.spec.js @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +describe('Directive: dashboard-layouts', function () { + + var $rootScope, element, options, childScope, DashboardState, LayoutStorage, $mockModal, $mockTimeout, toFn; + + // mock UI Sortable + beforeEach(function () { + angular.module('ui.sortable', []); + }); + + // load the directive's module + beforeEach(module('ui.dashboard', function($provide) { + $mockModal = { + open: function() {} + }; + $mockTimeout = function(fn, delay) { + toFn = fn; + }; + $provide.value('$uibModal', $mockModal); + $provide.value('$timeout', $mockTimeout); + })); + + beforeEach(inject(function ($compile, _$rootScope_, _DashboardState_, _LayoutStorage_) { + // services + $rootScope = _$rootScope_; + DashboardState = _DashboardState_; + LayoutStorage = _LayoutStorage_; + + // options + var widgetDefinitions = [ + { + name: 'wt-one', + template: '<div class="wt-one-value">{{2 + 2}}</div>' + }, + { + name: 'wt-two', + template: '<span class="wt-two-value">{{value}}</span>' + } + ]; + var defaultWidgets = _.clone(widgetDefinitions); + $rootScope.dashboardOptions = options = { + widgetButtons: true, + widgetDefinitions: widgetDefinitions, + defaultLayouts: [ + { + title: 'first', + active: true, + defaultWidgets: defaultWidgets + }, + { + title: 'second', + active: false, + defaultWidgets: defaultWidgets + } + ], + defaultWidgets: defaultWidgets, + storage: { + setItem: function(key, val) { + + }, + getItem: function(key) { + + }, + removeItem: function(key) { + + } + } + }; + $rootScope.value = 10; + + // element setup + element = $compile('<div dashboard-layouts="dashboardOptions"></div>')($rootScope); + $rootScope.$digest(); + childScope = element.scope(); + })); + + it('should not require storage', inject(function($compile) { + delete $rootScope.dashboardOptions.storage; + expect(function() { + var noStorageEl = $compile('<div dashboard-layouts="dashboardOptions"></div>')($rootScope); + $rootScope.$digest(); + }).not.toThrow(); + + })); + + it('should be able to use a different dashboard-layouts template', inject(function($compile, $templateCache) { + $templateCache.put( + 'myCustomTemplate.html', + '<ul class="my-custom-tabs layout-tabs">' + + '<li ng-repeat="layout in layouts" ng-class="{ active: layout.active }">' + + '<a ng-click="makeLayoutActive(layout)">' + + '<span ng-dblclick="editTitle(layout)" ng-show="!layout.editingTitle">{{layout.title}}</span>' + + '<form action="" class="layout-title" ng-show="layout.editingTitle" ng-submit="saveTitleEdit(layout)">' + + '<input type="text" ng-model="layout.title" class="form-control" data-layout="{{layout.id}}">' + + '</form>' + + '<span ng-click="removeLayout(layout)" class="glyphicon glyphicon-remove icon-erase"></span>' + + '<!-- <span class="glyphicon glyphicon-pencil"></span> -->' + + '<!-- <span class="glyphicon glyphicon-remove"></span> -->' + + '</a>' + + '</li>' + + '<li>' + + '<a ng-click="createNewLayout()">' + + '<span class="glyphicon glyphicon-plus"></span>' + + '</a>' + + '</li>' + + '</ul>' + + '<div ng-repeat="layout in layouts | filter:isActive" dashboard="layout.dashboard" templateUrl="template/dashboard.html"></div>' + ); + var customElement = $compile('<div dashboard-layouts="dashboardOptions" template-url="myCustomTemplate.html"></div>')($rootScope); + $rootScope.$digest(); + expect(customElement.find('ul.my-custom-tabs').length).toEqual(1); + + })); + + it('should set the first dashboard to active if there is not one already active', inject(function($compile) { + options.defaultLayouts[0].active = options.defaultLayouts[1].active = false; + element = $compile('<div dashboard-layouts="dashboardOptions"></div>')($rootScope); + $rootScope.$digest(); + childScope = element.scope(); + + var layouts = childScope.layouts; + var active; + for (var i = 0; i < layouts.length; i++) { + if (layouts[i].active) { + active = layouts[i]; + break; + } + }; + expect(active).not.toBeUndefined(); + })); + + describe('the createNewLayout method', function() { + + it('should call the add and save methods of LayoutStorage', function() { + spyOn(LayoutStorage.prototype, 'add'); + spyOn(LayoutStorage.prototype, 'save'); + + childScope.createNewLayout(); + expect(LayoutStorage.prototype.add).toHaveBeenCalled(); + expect(LayoutStorage.prototype.save).toHaveBeenCalled(); + }); + + it('should return the newly created layout object', function() { + var result = childScope.createNewLayout(); + expect(typeof result).toEqual('object'); + }); + + it('should set active=true on the newly created layout', function() { + var result = childScope.createNewLayout(); + expect(result.active).toEqual(true); + }); + + it('should set defaultWidgets to dashboardOptions.defaultWidgets if it is present', function() { + var result = childScope.createNewLayout(); + expect(result.defaultWidgets === options.defaultWidgets).toEqual(true); + }); + + it('should set defaultWidgets to an empty array if dashboardOptions.defaultWidgets is not present', inject(function($compile) { + delete options.defaultWidgets; + element = $compile('<div dashboard-layouts="dashboardOptions"></div>')($rootScope); + $rootScope.$digest(); + childScope = element.scope(); + var result = childScope.createNewLayout(); + expect(result.defaultWidgets).toEqual([]); + })); + + }); + + describe('the removeLayout method', function() { + + it('should call the remove and save methods of LayoutStorage', function() { + spyOn(LayoutStorage.prototype, 'remove'); + spyOn(LayoutStorage.prototype, 'save'); + + childScope.removeLayout(childScope.layouts[0]); + expect(LayoutStorage.prototype.remove).toHaveBeenCalled(); + expect(LayoutStorage.prototype.save).toHaveBeenCalled(); + }); + + it('should call remove with the layout it was passed', function() { + spyOn(LayoutStorage.prototype, 'remove'); + var layout = childScope.layouts[0]; + childScope.removeLayout(layout); + expect(LayoutStorage.prototype.remove.calls.argsFor(0)[0]).toEqual(layout); + }); + + }); + + describe('the makeLayoutActive method', function() { + + it('should call _makeLayoutActive if there is not a currently active dashboard with unsaved changes', function() { + spyOn(childScope, '_makeLayoutActive'); + var layout = childScope.layouts[1]; + childScope.makeLayoutActive(layout); + expect(childScope._makeLayoutActive).toHaveBeenCalled(); + }); + + describe('when there are unsaved changes on the current dashboard', function() { + + var current, options, successCb, errorCb, layout; + + beforeEach(function() { + current = childScope.layouts[0]; + current.dashboard.unsavedChangeCount = 1; + + spyOn($mockModal, 'open').and.callFake(function(arg) { + options = arg; + return { + result: { + then: function(success, error) { + successCb = success; + errorCb = error; + } + } + } + }); + + layout = childScope.layouts[1]; + childScope.makeLayoutActive(layout); + }); + + it('should create a modal', function() { + expect($mockModal.open).toHaveBeenCalled(); + }); + + it('should resolve layout to the layout to be made active', function() { + expect(options.resolve.layout()).toEqual(layout); + }); + + it('should provide a success callback that saves the current dashboard and then calls _makeLayoutActive', function() { + spyOn(current.dashboard, 'saveDashboard'); + spyOn(childScope, '_makeLayoutActive'); + successCb(); + expect(current.dashboard.saveDashboard).toHaveBeenCalled(); + expect(childScope._makeLayoutActive).toHaveBeenCalled(); + expect(childScope._makeLayoutActive.calls.argsFor(0)[0]).toEqual(layout); + }); + + it('should provide an error callback that only calls _makeLayoutActive', function() { + spyOn(current.dashboard, 'saveDashboard'); + spyOn(childScope, '_makeLayoutActive'); + errorCb(); + expect(current.dashboard.saveDashboard).not.toHaveBeenCalled(); + expect(childScope._makeLayoutActive).toHaveBeenCalled(); + expect(childScope._makeLayoutActive.calls.argsFor(0)[0]).toEqual(layout); + }); + + }); + + }); + + describe('the editTitle method', function() { + + it('should set the editingTitle attribute to true on the layout it is passed', function() { + var layout = { id: '1' }; + childScope.editTitle(layout); + $rootScope.$digest(); + expect(layout.editingTitle).toEqual(true); + toFn(); + }); + + }); + + describe('the saveTitleEdit method', function() { + + it('should set editingTitle to false', function() { + var layout = { id: '1' }; + childScope.saveTitleEdit(layout); + expect(layout.editingTitle).toEqual(false); + }); + + it('should call layoutStorage.save', function() { + var layout = { id: '1' }; + spyOn(LayoutStorage.prototype, 'save').and.callThrough(); + childScope.saveTitleEdit(layout); + expect(LayoutStorage.prototype.save).toHaveBeenCalled(); + }); + + }); + + describe('the saveLayouts method', function() { + + it('should call LayoutStorage.save', function() { + spyOn(LayoutStorage.prototype, 'save').and.callThrough(); + $rootScope.dashboardOptions.saveLayouts(); + expect(LayoutStorage.prototype.save).toHaveBeenCalled(); + }); + + it('should call LayoutStorage.save with true as the first arg', function() { + spyOn(LayoutStorage.prototype, 'save').and.callThrough(); + $rootScope.dashboardOptions.saveLayouts(); + expect(LayoutStorage.prototype.save.calls.argsFor(0)[0]).toEqual(true); + }); + + }); + describe('the proxy methods to active layout', function() { + + var mockDash, galSpy; + + beforeEach(function() { + mockDash = { + active: true, + dashboard: { + addWidget: function() {}, + loadWidgets: function() {}, + saveDashboard: function() {} + } + }; + spyOn(mockDash.dashboard, 'addWidget'); + spyOn(mockDash.dashboard, 'loadWidgets'); + spyOn(mockDash.dashboard, 'saveDashboard'); + galSpy = spyOn(LayoutStorage.prototype, 'getActiveLayout').and; + galSpy.returnValue(mockDash); + }); + + describe('the addWidget method', function() { + + it('should call dashboard.addWidget method of the active layout', function() { + options.addWidget(1,2,3); + expect(mockDash.dashboard.addWidget).toHaveBeenCalled(); + var firstCall = mockDash.dashboard.addWidget.calls.first(); + expect(firstCall.object).toEqual(mockDash.dashboard); + expect(firstCall.args).toEqual([1,2,3]); + }); + + it('should do nothing if there is no active layout', function() { + galSpy.returnValue(null); + expect(function() { + options.addWidget(); + }).not.toThrow(); + }); + + }); + + describe('the loadWidgets method', function() { + + it('should call dashboard.loadWidgets of the current layout', function() { + options.loadWidgets(1,2,3); + expect(mockDash.dashboard.loadWidgets).toHaveBeenCalled(); + var firstCall = mockDash.dashboard.loadWidgets.calls.first(); + expect(firstCall.object).toEqual(mockDash.dashboard); + expect(firstCall.args).toEqual([1,2,3]); + }); + + it('should do nothing if there is no active layout', function() { + galSpy.returnValue(null); + expect(function() { + options.loadWidgets(); + }).not.toThrow(); + }); + + }); + + describe('the saveDashboard method', function() { + + it('should call dashboard.saveDashboard of the current layout', function() { + options.saveDashboard(1,2,3); + expect(mockDash.dashboard.saveDashboard).toHaveBeenCalled(); + var firstCall = mockDash.dashboard.saveDashboard.calls.first(); + expect(firstCall.object).toEqual(mockDash.dashboard); + expect(firstCall.args).toEqual([1,2,3]); + }); + + it('should do nothing if there is no active layout', function() { + galSpy.returnValue(null); + expect(function() { + options.saveDashboard(); + }).not.toThrow(); + }); + + }); + + }); + +});
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/DashboardWidgetCtrl.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/DashboardWidgetCtrl.js new file mode 100644 index 00000000..9ac57b19 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/DashboardWidgetCtrl.js @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .controller('DashboardWidgetCtrl', ['$scope', '$element', '$compile', '$window', '$timeout', + function($scope, $element, $compile, $window, $timeout) { + + $scope.status = { + isopen: false + }; + + // Fills "container" with compiled view + $scope.makeTemplateString = function() { + + var widget = $scope.widget; + + // First, build template string + var templateString = ''; + + if (widget.templateUrl) { + + // Use ng-include for templateUrl + templateString = '<div ng-include="\'' + widget.templateUrl + '\'"></div>'; + + } else if (widget.template) { + + // Direct string template + templateString = widget.template; + + } else { + + // Assume attribute directive + templateString = '<div ' + widget.directive; + + // Check if data attribute was specified + if (widget.dataAttrName) { + widget.attrs = widget.attrs || {}; + widget.attrs[widget.dataAttrName] = 'widgetData'; + } + + // Check for specified attributes + if (widget.attrs) { + + // First check directive name attr + if (widget.attrs[widget.directive]) { + templateString += '="' + widget.attrs[widget.directive] + '"'; + } + + // Add attributes + _.each(widget.attrs, function(value, attr) { + + // make sure we aren't reusing directive attr + if (attr !== widget.directive) { + templateString += ' ' + attr + '="' + value + '"'; + } + + }); + } + templateString += '></div>'; + } + return templateString; + }; + + $scope.grabResizer = function(e) { + + var widget = $scope.widget; + var widgetElm = $element.find('.widget'); + + // ignore middle- and right-click + if (e.which !== 1) { + return; + } + + e.stopPropagation(); + e.originalEvent.preventDefault(); + + // get the starting horizontal position + var initX = e.clientX; + // console.log('initX', initX); + + // Get the current width of the widget and dashboard + var pixelWidth = widgetElm.width(); + var pixelHeight = widgetElm.height(); + var widgetStyleWidth = widget.containerStyle.width; + var widthUnits = widget.widthUnits; + var unitWidth = parseFloat(widgetStyleWidth); + + // create marquee element for resize action + var $marquee = angular.element('<div class="widget-resizer-marquee" style="height: ' + pixelHeight + 'px; width: ' + pixelWidth + 'px; z-index:'+ 200 +';"></div>'); + widgetElm.append($marquee); + // create an overlaying div to block other widgets in order to stop their iframe events from being triggered + var $marquee2 = angular.element('<div style=" position: absolute; top: 0; left: 0; height: ' + pixelHeight + 'px; width: ' + (pixelWidth+200) + 'px; z-index:'+ 100 +';"></div>'); + widgetElm.append($marquee2); + + // determine the unit/pixel ratio + var transformMultiplier = unitWidth / pixelWidth; + + // updates marquee with preview of new width + var mousemove = function(e) { + var curX = e.clientX; +// console.log(curX); +// console.log(e); + var pixelChange = curX - initX; + var newWidth = pixelWidth + pixelChange; + $marquee.css('width', newWidth + 'px'); + $marquee2.css('width', (newWidth + 200) + 'px'); + + }; + + // sets new widget width on mouseup + var mouseup = function(e) { + // remove listener and marquee + jQuery($window).off('mousemove', mousemove); + $marquee.remove(); + $marquee2.remove(); + + // calculate change in units + var curX = e.clientX; + var pixelChange = curX - initX; + var unitChange = Math.round(pixelChange * transformMultiplier * 100) / 100; + + // add to initial unit width + var newWidth = unitWidth * 1 + unitChange; + widget.setWidth(newWidth, widthUnits); + $scope.$emit('widgetChanged', widget); + $scope.$apply(); + $scope.$broadcast('widgetResized', { + width: newWidth + }); + }; + +// jQuery($window).on('mousemove', mousemove).one('mouseup', mouseup); + jQuery($window).on('mousemove', mousemove).one('mouseup', mouseup); + }; + + //TODO refactor + $scope.grabSouthResizer = function(e) { + var widgetElm = $element.find('.widget'); + + // ignore middle- and right-click + if (e.which !== 1) { + return; + } + + e.stopPropagation(); + e.originalEvent.preventDefault(); + + // get the starting horizontal position + var initY = e.clientY; + // console.log('initX', initX); + + // Get the current width of the widget and dashboard + var pixelWidth = widgetElm.width(); + var pixelHeight = widgetElm.height(); + + // create marquee element for resize action + var $marquee = angular.element('<div class="widget-resizer-marquee" style="height: ' + pixelHeight + 'px; width: ' + pixelWidth + 'px;"></div>'); + widgetElm.append($marquee); + + // updates marquee with preview of new height + var mousemove = function(e) { + var curY = e.clientY; + var pixelChange = curY - initY; + var newHeight = pixelHeight + pixelChange; + $marquee.css('height', newHeight + 'px'); + }; + + // sets new widget width on mouseup + var mouseup = function(e) { + // remove listener and marquee + jQuery($window).off('mousemove', mousemove); + $marquee.remove(); + + // calculate height change + var curY = e.clientY; + var pixelChange = curY - initY; + + //var widgetContainer = widgetElm.parent(); // widget container responsible for holding widget width and height + var widgetContainer = widgetElm.find('.widget-content'); + + var diff = pixelChange; + var height = parseInt(widgetContainer.css('height'), 10); + var newHeight = (height + diff); + + //$scope.widget.style.height = newHeight + 'px'; + + $scope.widget.setHeight(newHeight + 'px'); + + $scope.$emit('widgetChanged', $scope.widget); + $scope.$apply(); // make AngularJS to apply style changes + + $scope.$broadcast('widgetResized', { + height: newHeight + }); + }; + + jQuery($window).on('mousemove', mousemove).one('mouseup', mouseup); + }; + + // replaces widget title with input + $scope.editTitle = function(widget) { + var widgetElm = $element.find('.widget'); + widget.editingTitle = true; + // HACK: get the input to focus after being displayed. + $timeout(function() { + widgetElm.find('form.widget-title input:eq(0)').focus()[0].setSelectionRange(0, 9999); + }); + }; + + // saves whatever is in the title input as the new title + $scope.saveTitleEdit = function(widget) { + widget.editingTitle = false; + $scope.$emit('widgetChanged', widget); + }; + + $scope.compileTemplate = function() { + var container = $scope.findWidgetContainer($element); + var templateString = $scope.makeTemplateString(); + var widgetElement = angular.element(templateString); + + container.empty(); + container.append(widgetElement); + $compile(widgetElement)($scope); + }; + + $scope.findWidgetContainer = function(element) { + // widget placeholder is the first (and only) child of .widget-content + return element.find('.widget-content'); + }; + } + ]);
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/DashboardWidgetCtrl.spec.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/DashboardWidgetCtrl.spec.js new file mode 100644 index 00000000..55604646 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/DashboardWidgetCtrl.spec.js @@ -0,0 +1,164 @@ +'use strict'; + +describe('Controller: DashboardWidgetCtrl', function() { + + var $scope, $element, $timeout, injections; + + beforeEach(module('ui.dashboard')); + + beforeEach(inject(function($rootScope, $controller){ + $scope = $rootScope.$new(); + $element = angular.element('<div><div class="widget"></div></div>'); + $timeout = function timeout(fn) { + fn(); + }; + injections = { + $scope: $scope, + $element: $element, + $timeout: $timeout + }; + spyOn(injections, '$timeout'); + $controller('DashboardWidgetCtrl', injections); + })); + + describe('the makeTemplateString method', function() { + + it('should return a string', function() { + $scope.widget = { + templateUrl: 'some/template.html' + }; + expect(typeof $scope.makeTemplateString()).toEqual('string'); + }); + + it('should use ng-include if templateUrl is specified on widget, despite any other options', function() { + $scope.widget = { + templateUrl: 'some/template.html', + template: 'not this one', + directive: 'or-this', + attrs: { + something: 'awesome', + other: 'thing' + } + }; + expect($scope.makeTemplateString()).toMatch(/ng-include="'some\/template\.html'"/); + }); + + it('should return widget.template if specified, regardless of presence of directive or attrs', function() { + $scope.widget = { + template: '<div class="testing"></div>', + directive: 'no-good' + }; + expect($scope.makeTemplateString()).toEqual($scope.widget.template); + }); + + it('should use widget.directive as attribute directive', function() { + $scope.widget = { + directive: 'ng-awesome' + }; + expect($scope.makeTemplateString()).toEqual('<div ng-awesome></div>'); + }); + + it('should attach attributes if provided', function() { + $scope.widget = { + directive: 'ng-awesome', + attrs: { + 'ng-awesome': 'test1', + other: 'attr', + more: 'stuff' + } + }; + expect($scope.makeTemplateString()).toEqual('<div ng-awesome="test1" other="attr" more="stuff"></div>'); + }); + + it('should place widgetData into dataAttrName attribute if specified', function() { + $scope.widget = { + directive: 'ng-awesome', + attrs: { + 'ng-awesome': 'test1', + other: 'attr', + more: 'stuff' + }, + dataAttrName: 'data' + }; + expect($scope.makeTemplateString()).toEqual('<div ng-awesome="test1" other="attr" more="stuff" data="widgetData"></div>'); + }); + + it('should add attrs to the widget object if it does not exist and dataAttrName is specified', function() { + $scope.widget = { + directive: 'ng-awesome', + dataAttrName: 'data' + }; + expect($scope.makeTemplateString()).toEqual('<div ng-awesome data="widgetData"></div>'); + }); + + }); + + describe('the grabResizer method', function() { + + var evt, widget, WidgetModel; + + beforeEach(inject(function (_WidgetModel_) { + WidgetModel = _WidgetModel_; + })); + + beforeEach(function() { + evt = { + stopPropagation: jasmine.createSpy('stopPropagation'), + originalEvent: { + preventDefault: jasmine.createSpy('preventDefault') + }, + clientX: 100, + which: 1 + }; + $scope.widget = widget = new WidgetModel({ + style: { + width: '30%' + } + }); + }); + + it('should do nothing if event.which is not 1 (left click)', function() { + evt.which = 2; + $scope.grabResizer(evt); + expect(evt.stopPropagation).not.toHaveBeenCalled(); + }); + + it('should call stopPropagation and preventDefault', function() { + $scope.grabResizer(evt); + expect(evt.stopPropagation).toHaveBeenCalled(); + expect(evt.originalEvent.preventDefault).toHaveBeenCalled(); + }); + + it('should add a .widget-resizer-marquee element to the .widget element', function() { + $scope.grabResizer(evt); + expect($element.find('.widget-resizer-marquee').length).toBeGreaterThan(0); + }); + + }); + + describe('the editTitle method', function() { + + it('should set editingTitle=true on the widget object', function() { + var widget = {}; + $scope.editTitle(widget); + expect(widget.editingTitle).toEqual(true); + }); + + it('should call $timeout', function() { + var widget = {}; + $scope.editTitle(widget); + expect(injections.$timeout).toHaveBeenCalled(); + }); + + }); + + describe('the saveTitleEdit method', function() { + + it('should set editingTitle=false', function() { + var widget = { editingTitle: true }; + $scope.saveTitleEdit(widget); + expect(widget.editingTitle).toEqual(false); + }); + }); + +});
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/widget.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/widget.js new file mode 100644 index 00000000..f5a6ebef --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/widget.js @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .directive('widget', ['$injector', function ($injector) { + + return { + + controller: 'DashboardWidgetCtrl', + + link: function (scope) { + + var widget = scope.widget; + var dataModelType = widget.dataModelType; + + // set up data source + if (dataModelType) { + var DataModelConstructor; // data model constructor function + + if (angular.isFunction(dataModelType)) { + DataModelConstructor = dataModelType; + } else if (angular.isString(dataModelType)) { + $injector.invoke([dataModelType, function (DataModelType) { + DataModelConstructor = DataModelType; + }]); + } else { + throw new Error('widget dataModelType should be function or string'); + } + + var ds; + if (widget.dataModelArgs) { + ds = new DataModelConstructor(widget.dataModelArgs); + } else { + ds = new DataModelConstructor(); + } + widget.dataModel = ds; + ds.setup(widget, scope); + ds.init(); + scope.$on('$destroy', _.bind(ds.destroy,ds)); + } + + // Compile the widget template, emit add event + scope.compileTemplate(); + scope.$emit('widgetAdded', widget); + + } + + }; + }]); diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/widget.spec.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/widget.spec.js new file mode 100644 index 00000000..0997e071 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/widget.spec.js @@ -0,0 +1,104 @@ +// 'use strict'; + +describe('Directive: widget', function () { + + var element, scope, rootScope, isoScope, compile, provide; + + function Type() { + } + + Type.prototype = { + setup: function () { + }, + init: function () { + }, + destroy: function () { + } + }; + + beforeEach(function () { + spyOn(Type.prototype, 'setup'); + spyOn(Type.prototype, 'init'); + spyOn(Type.prototype, 'destroy'); + // define mock objects here + }); + + // load the directive's module + beforeEach(module('ui.dashboard', function ($provide, $controllerProvider) { + provide = $provide; + // Inject dependencies like this: + $controllerProvider.register('DashboardWidgetCtrl', function ($scope) { + + }); + + })); + + beforeEach(inject(function ($compile, $rootScope) { + // Cache these for reuse + rootScope = $rootScope; + compile = $compile; + + // Other setup, e.g. helper functions, etc. + + // Set up the outer scope + scope = $rootScope.$new(); + scope.widget = { + dataModelType: Type + }; + + compileTemplate = jasmine.createSpy('compileTemplate'); + scope.compileTemplate = compileTemplate; + })); + + function compileWidget() { + // Define and compile the element + element = angular.element('<div widget><div class="widget-content"></div></div>'); + element = compile(element)(scope); + scope.$digest(); + isoScope = element.isolateScope(); + } + + it('should create a new instance of dataModelType if provided in scope.widget', function () { + compileWidget(); + expect(scope.widget.dataModel instanceof Type).toBe(true); + }); + + it('should call setup and init on the new dataModel', function () { + compileWidget(); + expect(Type.prototype.setup).toHaveBeenCalled(); + expect(Type.prototype.init).toHaveBeenCalled(); + }); + + it('should call compile template', function () { + compileWidget(); + expect(scope.compileTemplate).toHaveBeenCalled(); + }); + + it('should create a new instance of dataModelType from string name', function () { + // register data model with $injector + provide.factory('StringNameDataModel', function () { + return Type; + }); + + scope.widget = { + dataModelType: 'StringNameDataModel' + }; + + compileWidget(); + + expect(scope.widget.dataModel instanceof Type).toBe(true); + expect(Type.prototype.setup).toHaveBeenCalled(); + expect(Type.prototype.init).toHaveBeenCalled(); + }); + + it('should validate data model type', function () { + scope.widget = { + dataModelType: {} + }; + + expect(function () { + compileWidget() + }).toThrowError(); + }); + +});
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/DashboardState.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/DashboardState.js new file mode 100644 index 00000000..67948ead --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/DashboardState.js @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .factory('DashboardState', ['$log', '$q', function ($log, $q) { + function DashboardState(storage, id, hash, widgetDefinitions, stringify) { + this.storage = storage; + this.id = id; + this.hash = hash; + this.widgetDefinitions = widgetDefinitions; + this.stringify = stringify; + } + + DashboardState.prototype = { + /** + * Takes array of widget instance objects, serializes, + * and saves state. + * + * @param {Array} widgets scope.widgets from dashboard directive + * @return {Boolean} true on success, false on failure + */ + save: function (widgets) { + + if (!this.storage) { + return true; + } + + var serialized = _.map(widgets, function (widget) { + return widget.serialize(); + }); + + var item = { widgets: serialized, hash: this.hash }; + + if (this.stringify) { + item = JSON.stringify(item); + } + + this.storage.setItem(this.id, item); + return true; + }, + + /** + * Loads dashboard state from the storage object. + * Can handle a synchronous response or a promise. + * + * @return {Array|Promise} Array of widget definitions or a promise + */ + load: function () { + + if (!this.storage) { + return null; + } + + var serialized; + + // try loading storage item + serialized = this.storage.getItem( this.id ); + + if (serialized) { + // check for promise + if (angular.isObject(serialized) && angular.isFunction(serialized.then)) { + return this._handleAsyncLoad(serialized); + } + // otherwise handle synchronous load + return this._handleSyncLoad(serialized); + } else { + return null; + } + }, + + _handleSyncLoad: function(serialized) { + + var deserialized, result = []; + + if (!serialized) { + return null; + } + + if (this.stringify) { + try { // to deserialize the string + + deserialized = JSON.parse(serialized); + + } catch (e) { + + // bad JSON, log a warning and return + $log.warn('Serialized dashboard state was malformed and could not be parsed: ', serialized); + return null; + + } + } + else { + deserialized = serialized; + } + + // check hash against current hash + if (deserialized.hash !== this.hash) { + + $log.info('Serialized dashboard from storage was stale (old hash: ' + deserialized.hash + ', new hash: ' + this.hash + ')'); + this.storage.removeItem(this.id); + return null; + + } + + // Cache widgets + var savedWidgetDefs = deserialized.widgets; + + // instantiate widgets from stored data + for (var i = 0; i < savedWidgetDefs.length; i++) { + + // deserialized object + var savedWidgetDef = savedWidgetDefs[i]; + + // widget definition to use + var widgetDefinition = this.widgetDefinitions.getByName(savedWidgetDef.name); + + // check for no widget + if (!widgetDefinition) { + // no widget definition found, remove and return false + $log.warn('Widget with name "' + savedWidgetDef.name + '" was not found in given widget definition objects'); + continue; + } + + // check widget-specific storageHash + if (widgetDefinition.hasOwnProperty('storageHash') && widgetDefinition.storageHash !== savedWidgetDef.storageHash) { + // widget definition was found, but storageHash was stale, removing storage + $log.info('Widget Definition Object with name "' + savedWidgetDef.name + '" was found ' + + 'but the storageHash property on the widget definition is different from that on the ' + + 'serialized widget loaded from storage. hash from storage: "' + savedWidgetDef.storageHash + '"' + + ', hash from WDO: "' + widgetDefinition.storageHash + '"'); + continue; + } + + // push instantiated widget to result array + result.push(savedWidgetDef); + } + + return result; + }, + + _handleAsyncLoad: function(promise) { + var self = this; + var deferred = $q.defer(); + promise.then( + // success + function(res) { + var result = self._handleSyncLoad(res); + if (result) { + deferred.resolve(result); + } else { + deferred.reject(result); + } + }, + // failure + function(res) { + deferred.reject(res); + } + ); + + return deferred.promise; + } + + }; + return DashboardState; + }]);
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.js new file mode 100644 index 00000000..3685fd3f --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.js @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .factory('LayoutStorage', function() { + + var noopStorage = { + setItem: function() { + + }, + getItem: function() { + + }, + removeItem: function() { + + } + }; + + + + function LayoutStorage(options) { + + var defaults = { + storage: noopStorage, + storageHash: '', + stringifyStorage: true + }; + + angular.extend(defaults, options); + angular.extend(options, defaults); + + this.id = options.storageId; + this.storage = options.storage; + this.storageHash = options.storageHash; + this.stringifyStorage = options.stringifyStorage; + this.widgetDefinitions = options.widgetDefinitions; + this.defaultLayouts = options.defaultLayouts; + this.lockDefaultLayouts = options.lockDefaultLayouts; + this.widgetButtons = options.widgetButtons; + this.explicitSave = options.explicitSave; + this.defaultWidgets = options.defaultWidgets; + this.settingsModalOptions = options.settingsModalOptions; + this.onSettingsClose = options.onSettingsClose; + this.onSettingsDismiss = options.onSettingsDismiss; + this.options = options; + this.options.unsavedChangeCount = 0; + + this.layouts = []; + this.states = {}; + this.load(); + this._ensureActiveLayout(); + } + + LayoutStorage.prototype = { + + add: function(layouts) { + if (!angular.isArray(layouts)) { + layouts = [layouts]; + } + var self = this; + angular.forEach(layouts, function(layout) { + layout.dashboard = layout.dashboard || {}; + layout.dashboard.storage = self; + layout.dashboard.storageId = layout.id = self._getLayoutId.call(self,layout); + layout.dashboard.widgetDefinitions = layout.widgetDefinitions || self.widgetDefinitions; + layout.dashboard.stringifyStorage = false; + layout.dashboard.defaultWidgets = layout.defaultWidgets || self.defaultWidgets; + layout.dashboard.widgetButtons = self.widgetButtons; + layout.dashboard.explicitSave = self.explicitSave; + layout.dashboard.settingsModalOptions = self.settingsModalOptions; + layout.dashboard.onSettingsClose = self.onSettingsClose; + layout.dashboard.onSettingsDismiss = self.onSettingsDismiss; + self.layouts.push(layout); + }); + }, + + remove: function(layout) { + var index = this.layouts.indexOf(layout); + if (index >= 0) { + this.layouts.splice(index, 1); + delete this.states[layout.id]; + + // check for active + if (layout.active && this.layouts.length) { + var nextActive = index > 0 ? index - 1 : 0; + this.layouts[nextActive].active = true; + } + } + }, + + save: function() { + + var state = { + layouts: this._serializeLayouts(), + states: this.states, + storageHash: this.storageHash + }; + + if (this.stringifyStorage) { + state = JSON.stringify(state); + } + + this.storage.setItem(this.id, state); + this.options.unsavedChangeCount = 0; + }, + + load: function() { + + var serialized = this.storage.getItem(this.id); + + this.clear(); + + if (serialized) { + // check for promise + if (angular.isObject(serialized) && angular.isFunction(serialized.then)) { + this._handleAsyncLoad(serialized); + } else { + this._handleSyncLoad(serialized); + } + } else { + this._addDefaultLayouts(); + } + }, + + clear: function() { + this.layouts = []; + this.states = {}; + }, + + setItem: function(id, value) { + this.states[id] = value; + this.save(); + }, + + getItem: function(id) { + return this.states[id]; + }, + + removeItem: function(id) { + delete this.states[id]; + this.save(); + }, + + getActiveLayout: function() { + var len = this.layouts.length; + for (var i = 0; i < len; i++) { + var layout = this.layouts[i]; + if (layout.active) { + return layout; + } + } + return false; + }, + + _addDefaultLayouts: function() { + var self = this; + var defaults = this.lockDefaultLayouts ? { locked: true } : {}; + angular.forEach(this.defaultLayouts, function(layout) { + self.add(angular.extend(_.clone(defaults), layout)); + }); + }, + + _serializeLayouts: function() { + var result = []; + angular.forEach(this.layouts, function(l) { + result.push({ + title: l.title, + id: l.id, + active: l.active, + locked: l.locked, + defaultWidgets: l.dashboard.defaultWidgets + }); + }); + return result; + }, + + _handleSyncLoad: function(serialized) { + + var deserialized; + + if (this.stringifyStorage) { + try { + + deserialized = JSON.parse(serialized); + + } catch (e) { + this._addDefaultLayouts(); + return; + } + } else { + + deserialized = serialized; + + } + + if (this.storageHash !== deserialized.storageHash) { + this._addDefaultLayouts(); + return; + } + this.states = deserialized.states; + this.add(deserialized.layouts); + }, + + _handleAsyncLoad: function(promise) { + var self = this; + promise.then( + angular.bind(self, this._handleSyncLoad), + angular.bind(self, this._addDefaultLayouts) + ); + }, + + _ensureActiveLayout: function() { + for (var i = 0; i < this.layouts.length; i++) { + var layout = this.layouts[i]; + if (layout.active) { + return; + } + } + if (this.layouts[0]) { + this.layouts[0].active = true; + } + }, + + _getLayoutId: function(layout) { + if (layout.id) { + return layout.id; + } + var max = 0; + for (var i = 0; i < this.layouts.length; i++) { + var id = this.layouts[i].id; + max = Math.max(max, id * 1); + } + return max + 1; + } + + }; + return LayoutStorage; + });
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.spec.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.spec.js new file mode 100644 index 00000000..3310cad9 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.spec.js @@ -0,0 +1,692 @@ +'use strict'; + +describe('Factory: LayoutStorage', function () { + + // mock UI Sortable + beforeEach(function () { + angular.module('ui.sortable', []); + }); + + // load the service's module + beforeEach(module('ui.dashboard')); + + // instantiate service + var LayoutStorage; + beforeEach(inject(function (_LayoutStorage_) { + LayoutStorage = _LayoutStorage_; + })); + + describe('the constructor', function() { + + var storage, options; + + beforeEach(function() { + options = { + storageId: 'testingStorage', + storage: { + setItem: function(key, value) { + + }, + getItem: function(key) { + + }, + removeItem: function(key) { + + } + }, + storageHash: 'ds5f9d1f', + stringifyStorage: true, + widgetDefinitions: [ + + ], + defaultLayouts: [ + {title: 'something'}, + {title: 'something'}, + {title: 'something'} + ], + widgetButtons: false, + explicitSave: false, + settingsModalOptions: {}, + onSettingsClose: function() { + + }, + onSettingsDismiss: function() { + + } + }; + storage = new LayoutStorage(options); + }); + + it('should provide an empty implementation of storage if it is not provided', function() { + delete options.storage; + var stateless = new LayoutStorage(options); + var noop = stateless.storage; + angular.forEach(['setItem', 'getItem', 'removeItem'], function(method) { + expect(typeof noop[method]).toEqual('function'); + expect(noop[method]).not.toThrow(); + noop[method](); + }); + }); + + it('should set a subset of the options directly on the LayoutStorage instance itself', function() { + var properties = { + id: 'storageId', + storage: 'storage', + storageHash: 'storageHash', + stringifyStorage: 'stringifyStorage', + widgetDefinitions: 'widgetDefinitions', + defaultLayouts: 'defaultLayouts', + widgetButtons: 'widgetButtons', + explicitSave: 'explicitSave', + settingsModalOptions: 'settingsModalOptions', + onSettingsClose: 'onSettingsClose', + onSettingsDismiss: 'onSettingsDismiss' + }; + + angular.forEach(properties, function(val, key) { + expect( storage[key] ).toEqual( options[val] ); + }); + + }); + + it('should set stringify as true by default', function() { + delete options.stringifyStorage; + storage = new LayoutStorage(options); + expect(storage.stringifyStorage).toEqual(true); + }); + + it('should allow stringify to be overridden by option', function() { + options.stringifyStorage = false; + storage = new LayoutStorage(options); + expect(storage.stringifyStorage).toEqual(false); + }); + + it('should create a layouts array and states object', function() { + expect(storage.layouts instanceof Array).toEqual(true); + expect(typeof storage.states).toEqual('object'); + }); + + it('should call load', function() { + spyOn(LayoutStorage.prototype, 'load'); + storage = new LayoutStorage(options); + expect(LayoutStorage.prototype.load).toHaveBeenCalled(); + }); + + }); + + describe('the load method', function() { + + var options, storage; + + beforeEach(function() { + options = { + storageId: 'testingStorage', + storage: { + setItem: function(key, value) { + + }, + getItem: function(key) { + + }, + removeItem: function(key) { + + } + }, + storageHash: 'ds5f9d1f', + stringifyStorage: true, + widgetDefinitions: [ + + ], + defaultLayouts: [ + {title: 'something'}, + {title: 'something'}, + {title: 'something'} + ], + widgetButtons: false, + explicitSave: false + } + storage = new LayoutStorage(options); + }); + + it('should use the default layouts if no stored info was found', function() { + expect(storage.layouts.length).toEqual(options.defaultLayouts.length); + }); + + it('should clone default layouts rather than use them directly', function() { + expect(storage.layouts.indexOf(options.defaultLayouts[0])).toEqual(-1); + }); + + it('should use the result from getItem for layouts.', function() { + spyOn(options.storage, 'getItem').and.returnValue(JSON.stringify({ + storageHash: 'ds5f9d1f', + layouts: [ + { id: 0, title: 'title', defaultWidgets: [], active: true }, + { id: 1, title: 'title2', defaultWidgets: [], active: false }, + { id: 2, title: 'title3', defaultWidgets: [], active: false }, + { id: 3, title: 'custom', defaultWidgets: [], active: false } + ], + states: { + 0: {}, + 1: {}, + 2: {} + } + })); + storage.load(); + expect(storage.layouts.map(function(l) {return l.title})).toEqual(['title', 'title2', 'title3', 'custom']); + }); + + it('should NOT use result from getItem for layouts if the storageHash doesnt match', function() { + spyOn(options.storage, 'getItem').and.returnValue(JSON.stringify({ + storageHash: 'alskdjf02iej', + layouts: [ + { id: 0, title: 'title', defaultWidgets: [], active: true }, + { id: 1, title: 'title2', defaultWidgets: [], active: false }, + { id: 2, title: 'title3', defaultWidgets: [], active: false }, + { id: 3, title: 'custom', defaultWidgets: [], active: false } + ], + states: { + 0: {}, + 1: {}, + 2: {} + } + })); + storage.load(); + expect(storage.layouts.map(function(l) {return l.title})).toEqual(['something', 'something', 'something']); + }); + + it('should be able to handle async loading via promise', inject(function($rootScope,$q) { + var deferred = $q.defer(); + spyOn(options.storage, 'getItem').and.returnValue(deferred.promise); + storage.load(); + expect(storage.layouts).toEqual([]); + deferred.resolve(JSON.stringify({ + storageHash: 'ds5f9d1f', + layouts: [ + { id: 0, title: 'title', defaultWidgets: [], active: true }, + { id: 1, title: 'title2', defaultWidgets: [], active: false }, + { id: 2, title: 'title3', defaultWidgets: [], active: false }, + { id: 3, title: 'custom', defaultWidgets: [], active: false } + ], + states: { + 0: {}, + 1: {}, + 2: {} + } + })); + $rootScope.$apply(); + expect(storage.layouts.map(function(l) {return l.title})).toEqual(['title', 'title2', 'title3', 'custom']); + })); + + it('should load defaults if the deferred is rejected', inject(function($rootScope,$q) { + var deferred = $q.defer(); + spyOn(options.storage, 'getItem').and.returnValue(deferred.promise); + storage.load(); + deferred.reject(); + $rootScope.$apply(); + expect(storage.layouts.map(function(l) {return l.title})).toEqual(['something', 'something', 'something']); + })); + + it('should load defaults if the json is malformed', inject(function($rootScope,$q) { + var deferred = $q.defer(); + spyOn(options.storage, 'getItem').and.returnValue(deferred.promise); + storage.load(); + expect(storage.layouts).toEqual([]); + deferred.resolve(JSON.stringify({ + storageHash: 'ds5f9d1f', + layouts: [ + { id: 0, title: 'title', defaultWidgets: [], active: true }, + { id: 1, title: 'title2', defaultWidgets: [], active: false }, + { id: 2, title: 'title3', defaultWidgets: [], active: false }, + { id: 3, title: 'custom', defaultWidgets: [], active: false } + ], + states: { + 0: {}, + 1: {}, + 2: {} + } + }).replace('{','{{')); + $rootScope.$apply(); + expect(storage.layouts.map(function(l) {return l.title})).toEqual(['something', 'something', 'something']); + })); + + it('should not try to JSON.parse the result if stringifyStorage is false.', function() { + options.stringifyStorage = false; + storage = new LayoutStorage(options); + spyOn(options.storage, 'getItem').and.returnValue({ + storageHash: 'ds5f9d1f', + layouts: [ + { id: 0, title: 'title', defaultWidgets: [], active: true }, + { id: 1, title: 'title2', defaultWidgets: [], active: false }, + { id: 2, title: 'title3', defaultWidgets: [], active: false }, + { id: 3, title: 'custom', defaultWidgets: [], active: false } + ], + states: { + 0: {}, + 1: {}, + 2: {} + } + }); + storage.load(); + expect(storage.layouts.map(function(l) {return l.title})).toEqual(['title', 'title2', 'title3', 'custom']); + }); + + }); + + describe('the add method', function() { + + var storage, options; + + beforeEach(function() { + options = { + storageId: 'testingStorage', + storage: { + setItem: function(key, value) { + + }, + getItem: function(key) { + + }, + removeItem: function(key) { + + } + }, + storageHash: 'ds5f9d1f', + stringifyStorage: true, + widgetDefinitions: [ + + ], + defaultLayouts: [], + widgetButtons: false, + explicitSave: false + } + + spyOn(LayoutStorage.prototype, 'load' ); + + storage = new LayoutStorage(options); + + }); + + it('should add to storage.layouts', function() { + var newLayout = { title: 'my-layout' }; + storage.add(newLayout); + expect(storage.layouts[0]).toEqual(newLayout); + }); + + it('should be able to take an array of new layouts', function() { + var newLayouts = [ { title: 'my-layout' }, { title: 'my-layout-2' } ]; + storage.add(newLayouts); + expect(storage.layouts.length).toEqual(2); + expect(storage.layouts.indexOf(newLayouts[0])).not.toEqual(-1); + expect(storage.layouts.indexOf(newLayouts[1])).not.toEqual(-1); + }); + + it('should look for defaultWidgets on storage options if not supplied on layout definition', function() { + options.defaultWidgets = [{name: 'a'}, {name: 'b'}, {name: 'c'}]; + storage = new LayoutStorage(options); + + var newLayouts = [ { title: 'my-layout', defaultWidgets: [] }, { title: 'my-layout-2' } ]; + storage.add(newLayouts); + expect(newLayouts[0].dashboard.defaultWidgets === newLayouts[0].defaultWidgets).toEqual(true); + expect(newLayouts[1].dashboard.defaultWidgets === options.defaultWidgets).toEqual(true); + }); + + it('should use defaultWidgets if supplied in the layout definition', function() { + options.defaultWidgets = [{name: 'a'}, {name: 'b'}, {name: 'c'}]; + storage = new LayoutStorage(options); + + var newLayouts = [ { title: 'my-layout', defaultWidgets: [] }, { title: 'my-layout-2' } ]; + storage.add(newLayouts); + expect(newLayouts[0].dashboard.defaultWidgets).toEqual([]); + expect(newLayouts[1].dashboard.defaultWidgets).toEqual(options.defaultWidgets); + }); + + it('should look for widgetDefinitions on storage options if not supplied on layout definition', function() { + options.widgetDefinitions = [{name: 'a'}, {name: 'b'}, {name: 'c'}]; + storage = new LayoutStorage(options); + + var newLayouts = [ { title: 'my-layout', widgetDefinitions: [] }, { title: 'my-layout-2' } ]; + storage.add(newLayouts); + expect(newLayouts[0].dashboard.widgetDefinitions === newLayouts[0].widgetDefinitions).toEqual(true); + expect(newLayouts[1].dashboard.widgetDefinitions === options.widgetDefinitions).toEqual(true); + }); + + it('should use widgetDefinitions if supplied in the layout definition', function() { + options.widgetDefinitions = [{name: 'a'}, {name: 'b'}, {name: 'c'}]; + storage = new LayoutStorage(options); + + var newLayouts = [ { title: 'my-layout', widgetDefinitions: [] }, { title: 'my-layout-2' } ]; + storage.add(newLayouts); + expect(newLayouts[0].dashboard.widgetDefinitions).toEqual([]); + expect(newLayouts[1].dashboard.widgetDefinitions).toEqual(options.widgetDefinitions); + }); + + }); + + describe('the remove method', function() { + + var storage, options; + + beforeEach(function() { + options = { + storageId: 'testingStorage', + storageHash: 'ds5f9d1f', + stringifyStorage: true, + widgetDefinitions: [ + { name: 'A' }, + { name: 'B' }, + { name: 'C' } + ], + defaultLayouts: [ + { title: '1' }, + { title: '2', active: true }, + { title: '3' } + ], + widgetButtons: false, + explicitSave: false + } + + storage = new LayoutStorage(options); + }); + + it('should remove the supplied layout', function() { + var layout = storage.layouts[1]; + storage.remove(layout); + expect(storage.layouts.indexOf(layout)).toEqual(-1); + }); + + it('should delete the state', function() { + var layout = storage.layouts[1]; + storage.setItem(layout.id, {}); + storage.remove(layout); + expect(storage.states[layout.id]).toBeUndefined(); + }); + + it('should do nothing if layout is not in layouts', function() { + var layout = {}; + var before = storage.layouts.length; + storage.remove(layout); + var after = storage.layouts.length; + expect(before).toEqual(after); + }); + + it('should set another dashboard to active if the layout removed was active', function() { + var layout = storage.layouts[1]; + storage.remove(layout); + expect(storage.layouts[0].active || storage.layouts[1].active).toEqual(true); + }); + + it('should set the layout at index 0 to active if the removed layout was 0', function() { + storage.layouts[1].active = false; + storage.layouts[0].active = true; + storage.remove(storage.layouts[0]); + expect(storage.layouts[0].active).toEqual(true); + }); + + it('should not change the active layout if it was not the one that got removed', function() { + var active = storage.layouts[1]; + var layout = storage.layouts[0]; + storage.remove(layout); + expect(active.active).toEqual(true); + }); + + }); + + describe('the save method', function() { + + var options, storage; + + beforeEach(function() { + options = { + storageId: 'testingStorage', + storage: { + setItem: function(key, value) { + + }, + getItem: function(key) { + + }, + removeItem: function(key) { + + } + }, + storageHash: 'ds5f9d1f', + stringifyStorage: true, + widgetDefinitions: [ + + ], + defaultLayouts: [ + {title: 'something'}, + {title: 'something'}, + {title: 'something'} + ], + widgetButtons: false, + explicitSave: false + } + storage = new LayoutStorage(options); + }); + + it('should call options.storage.setItem with a stringified object', function() { + spyOn(options.storage, 'setItem' ); + storage.save(); + expect(options.storage.setItem).toHaveBeenCalled(); + expect(options.storage.setItem.calls.argsFor(0)[0]).toEqual(storage.id); + expect(typeof options.storage.setItem.calls.argsFor(0)[1]).toEqual('string'); + expect(function(){ + JSON.parse(options.storage.setItem.calls.argsFor(0)[1]); + }).not.toThrow(); + }); + + it('should save an object that has layouts, states, and storageHash', function() { + spyOn(options.storage, 'setItem' ); + storage.save(); + var obj = JSON.parse(options.storage.setItem.calls.argsFor(0)[1]); + expect(obj.hasOwnProperty('layouts')).toEqual(true); + expect(obj.layouts instanceof Array).toEqual(true); + expect(obj.hasOwnProperty('states')).toEqual(true); + expect(typeof obj.states).toEqual('object'); + expect(obj.hasOwnProperty('storageHash')).toEqual(true); + expect(typeof obj.storageHash).toEqual('string'); + }); + + it('should call options.storage.setItem with an object when stringifyStorage is false', function() { + options.stringifyStorage = false; + storage = new LayoutStorage(options); + spyOn(options.storage, 'setItem' ); + storage.save(); + expect(options.storage.setItem).toHaveBeenCalled(); + expect(options.storage.setItem.calls.argsFor(0)[0]).toEqual(storage.id); + expect(typeof options.storage.setItem.calls.argsFor(0)[1]).toEqual('object'); + }); + + }); + + describe('the setItem method', function() { + + var options, storage; + + beforeEach(function() { + options = { + storageId: 'testingStorage', + storage: { + setItem: function(key, value) { + + }, + getItem: function(key) { + + }, + removeItem: function(key) { + + } + }, + storageHash: 'ds5f9d1f', + stringifyStorage: true, + widgetDefinitions: [ + + ], + defaultLayouts: [ + {title: 'something'}, + {title: 'something'}, + {title: 'something'} + ], + widgetButtons: false, + explicitSave: false + } + storage = new LayoutStorage(options); + }); + + it('should set storage.states[id] to the second argument', function() { + var state = { some: 'thing'}; + storage.setItem('id', state); + expect(storage.states.id).toEqual(state); + }); + + it('should call save', function() { + spyOn(storage, 'save'); + var state = { some: 'thing'}; + storage.setItem('id', state); + expect(storage.save).toHaveBeenCalled(); + }); + + }); + + describe('the getItem method', function() { + + var options, storage; + + beforeEach(function() { + options = { + storageId: 'testingStorage', + storage: { + setItem: function(key, value) { + + }, + getItem: function(key) { + + }, + removeItem: function(key) { + + } + }, + storageHash: 'ds5f9d1f', + stringifyStorage: true, + widgetDefinitions: [ + + ], + defaultLayouts: [ + {title: 'something'}, + {title: 'something'}, + {title: 'something'} + ], + widgetButtons: false, + explicitSave: false + } + storage = new LayoutStorage(options); + }); + + it('should return states[id]', function() { + storage.states['myId'] = {}; + var result = storage.getItem('myId'); + expect(result === storage.states['myId']).toEqual(true); + }); + + }); + + describe('the getActiveLayout method', function() { + var options, storage; + + beforeEach(function() { + options = { + storageId: 'testingStorage', + storage: { + setItem: function(key, value) { + + }, + getItem: function(key) { + + }, + removeItem: function(key) { + + } + }, + storageHash: 'ds5f9d1f', + stringifyStorage: true, + widgetDefinitions: [ + + ], + defaultLayouts: [ + {title: 'i am active', active: true}, + {title: 'i am not'}, + {title: 'me neither'} + ], + widgetButtons: false, + explicitSave: false + } + storage = new LayoutStorage(options); + }); + + it('should return the layout with active:true', function() { + var layout = storage.getActiveLayout(); + expect(layout.title).toEqual('i am active'); + }); + + it('should return false if no layout is active', function() { + var layout = storage.getActiveLayout(); + layout.active = false; + var result = storage.getActiveLayout(); + expect(result).toEqual(false); + }); + + }); + + describe('the removeItem', function() { + + var options, storage; + + beforeEach(function() { + options = { + storageId: 'testingStorage', + storage: { + setItem: function(key, value) { + + }, + getItem: function(key) { + + }, + removeItem: function(key) { + + } + }, + storageHash: 'ds5f9d1f', + stringifyStorage: true, + widgetDefinitions: [ + + ], + defaultLayouts: [ + {title: 'i am active', active: true}, + {title: 'i am not'}, + {title: 'me neither'} + ], + widgetButtons: false, + explicitSave: false + } + storage = new LayoutStorage(options); + }); + + it('should remove states[id]', function() { + var state = {}; + storage.setItem('1', state); + storage.removeItem('1'); + expect(storage.states['1']).toBeUndefined(); + }); + + it('should call save', function() { + spyOn(storage, 'save'); + var state = {}; + storage.setItem('1', state); + storage.removeItem('1'); + expect(storage.save).toHaveBeenCalled(); + }); + + }); + +});
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDataModel.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDataModel.js new file mode 100644 index 00000000..547f2e96 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDataModel.js @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .factory('WidgetDataModel', function () { + function WidgetDataModel() { + } + + WidgetDataModel.prototype = { + setup: function (widget, scope) { + this.dataAttrName = widget.dataAttrName; + this.dataModelOptions = widget.dataModelOptions; + this.widgetScope = scope; + }, + + updateScope: function (data) { + this.widgetScope.widgetData = data; + }, + + init: function () { + // to be overridden by subclasses + }, + + destroy: function () { + // to be overridden by subclasses + } + }; + + return WidgetDataModel; + });
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDefCollection.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDefCollection.js new file mode 100644 index 00000000..27765440 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDefCollection.js @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .factory('WidgetDefCollection', function () { + + function convertToDefinition(d) { + if (typeof d === 'function') { + return new d(); + } + return d; + } + + function WidgetDefCollection(widgetDefs) { + + widgetDefs = widgetDefs.map(convertToDefinition); + + this.push.apply(this, widgetDefs); + + // build (name -> widget definition) map for widget lookup by name + var map = {}; + _.each(widgetDefs, function (widgetDef) { + map[widgetDef.name] = widgetDef; + }); + this.map = map; + } + + WidgetDefCollection.prototype = Object.create(Array.prototype); + + WidgetDefCollection.prototype.getByName = function (name) { + return this.map[name]; + }; + + WidgetDefCollection.prototype.add = function(def) { + def = convertToDefinition(def); + this.push(def); + this.map[def.name] = def; + }; + + return WidgetDefCollection; + }); diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.js new file mode 100644 index 00000000..c378d3b6 --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.js @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .factory('WidgetModel', function ($log) { + + function defaults() { + return { + title: 'Widget', + style: {}, + size: {}, + enableVerticalResize: true, + containerStyle: { width: '33%' }, // default width + contentStyle: {} + }; + }; + + // constructor for widget model instances + function WidgetModel(widgetDefinition, overrides) { + + // Extend this with the widget definition object with overrides merged in (deep extended). + angular.extend(this, defaults(), _.merge(angular.copy(widgetDefinition), overrides)); + + this.updateContainerStyle(this.style); + + if (!this.templateUrl && !this.template && !this.directive) { + this.directive = widgetDefinition.name; + } + + if (this.size && _.has(this.size, 'height')) { + this.setHeight(this.size.height); + } + + if (this.style && _.has(this.style, 'width')) { //TODO deprecate style attribute + this.setWidth(this.style.width); + } + + if (this.size && _.has(this.size, 'width')) { + this.setWidth(this.size.width); + } + } + + WidgetModel.prototype = { + // sets the width (and widthUnits) + setWidth: function (width, units) { + width = width.toString(); + units = units || width.replace(/^[-\.\d]+/, '') || '%'; + + this.widthUnits = units; + width = parseFloat(width); + + // check with min width if set, unit refer to width's unit + if (this.size && _.has(this.size, 'minWidth')) { + width = _.max([parseFloat(this.size.minWidth), width]); + } + + if (width < 0 || isNaN(width)) { + $log.warn('malhar-angular-dashboard: setWidth was called when width was ' + width); + return false; + } + + if (units === '%') { + width = Math.min(100, width); + width = Math.max(0, width); + } + + this.containerStyle.width = width + '' + units; + + this.updateSize(this.containerStyle); + + return true; + }, + + setHeight: function (height) { + this.contentStyle.height = height; + this.updateSize(this.contentStyle); + }, + + setStyle: function (style) { + this.style = style; + this.updateContainerStyle(style); + }, + + updateSize: function (size) { + angular.extend(this.size, size); + }, + + updateContainerStyle: function (style) { + angular.extend(this.containerStyle, style); + }, + serialize: function() { + return _.pick(this, ['title', 'name', 'report_id', 'hideGrid', 'showChart' ,'rcloud_url','reportData','style', 'size', 'dataModelOptions', 'attrs', 'storageHash']); + } + }; + + return WidgetModel; + });
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.spec.js b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.spec.js new file mode 100644 index 00000000..151e560a --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.spec.js @@ -0,0 +1,156 @@ +'use strict'; + +describe('Factory: WidgetModel', function () { + + // load the service's module + beforeEach(module('ui.dashboard')); + + // instantiate service + var WidgetModel; + beforeEach(inject(function (_WidgetModel_) { + WidgetModel = _WidgetModel_; + })); + + it('should be a function', function() { + expect(typeof WidgetModel).toEqual('function'); + }); + + describe('the constructor', function() { + var m, Class, Class2, overrides; + + beforeEach(function() { + Class = { + name: 'TestWidget', + attrs: {}, + dataAttrName: 'attr-name', + dataModelType: function TestType() {}, + dataModelOptions: {}, + style: { width: '10em' }, + settingsModalOptions: {}, + onSettingsClose: function() {}, + onSettingsDismiss: function() {}, + funkyChicken: { + cool: false, + fun: true + } + }; + + Class2 = { + name: 'TestWidget2', + attrs: {}, + dataAttrName: 'attr-name', + dataModelType: function TestType() {}, + dataModelOptions: {}, + style: { width: '10em' }, + templateUrl: 'my/url.html', + template: '<div>some template</div>' + }; + + overrides = { + style: { + width: '15em' + } + }; + spyOn(WidgetModel.prototype, 'setWidth'); + m = new WidgetModel(Class, overrides); + }); + + it('should copy class defaults, so that changes on an instance do not change the Class', function() { + m.style.width = '20em'; + expect(Class.style.width).toEqual('10em'); + }); + + it('should call setWidth', function() { + expect(WidgetModel.prototype.setWidth).toHaveBeenCalled(); + }); + + it('should take overrides as precedent over Class defaults', function() { + expect(m.style.width).toEqual('15em'); + }); + + it('should copy arbitrary data from the widget definition', function() { + expect(m.funkyChicken.cool).toEqual(false); + expect(m.funkyChicken.fun).toEqual(true); + expect(m.funkyChicken===Class.funkyChicken).toEqual(false); + }); + + it('should set templateUrl if and only if it is present on Class', function() { + var m2 = new WidgetModel(Class2, overrides); + expect(m2.templateUrl).toEqual('my/url.html'); + }); + + it('should set template if and only if it is present on Class', function() { + delete Class2.templateUrl; + var m2 = new WidgetModel(Class2, overrides); + expect(m2.template).toEqual('<div>some template</div>'); + }); + + it('should look for directive if neither templateUrl nor template is found on Class', function() { + delete Class2.templateUrl; + delete Class2.template; + Class2.directive = 'ng-bind'; + var m2 = new WidgetModel(Class2, overrides); + expect(m2.directive).toEqual('ng-bind'); + }); + + it('should set the name as directive if templateUrl, template, and directive are not defined', function() { + delete Class2.templateUrl; + delete Class2.template; + var m2 = new WidgetModel(Class2, overrides); + expect(m2.directive).toEqual('TestWidget2'); + }); + + it('should not require overrides', function() { + var fn = function() { + var m2 = new WidgetModel(Class); + } + expect(fn).not.toThrow(); + }); + + it('should copy references to settingsModalOptions, onSettingsClose, onSettingsDismiss', function() { + var m = new WidgetModel(Class); + expect(m.settingsModalOptions).toEqual(Class.settingsModalOptions); + expect(m.onSettingsClose).toEqual(Class.onSettingsClose); + expect(m.onSettingsDismiss).toEqual(Class.onSettingsDismiss); + }); + + }); + + describe('setWidth method', function() { + + var context, setWidth; + + beforeEach(function() { + context = new WidgetModel({}); + setWidth = WidgetModel.prototype.setWidth; + }); + + it('should take one argument as a string with units', function() { + setWidth.call(context, '100px'); + expect(context.containerStyle.width).toEqual('100px'); + }); + + it('should take two args as a number and string as units', function() { + setWidth.call(context, 100, 'px'); + expect(context.containerStyle.width).toEqual('100px'); + }); + + it('should return false and not set anything if width is less than 0', function() { + var result = setWidth.call(context, -100, 'em'); + expect(result).toEqual(false); + expect(context.containerStyle.width).not.toEqual('-100em'); + }); + + it('should assume % if no unit is given', function() { + setWidth.call(context, 50); + expect(context.containerStyle.width).toEqual('50%'); + }); + + it('should force greater than 0% and less than or equal 100%', function() { + setWidth.call(context, '110%'); + expect(context.containerStyle.width).toEqual('100%'); + }); + + }); + +});
\ No newline at end of file diff --git a/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/report-dashboard.html b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/report-dashboard.html new file mode 100644 index 00000000..8fecfb7b --- /dev/null +++ b/usecaseui-common/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/report-dashboard.html @@ -0,0 +1,220 @@ +<!doctype html> +<html> + <head> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <meta http-equiv="cache-control" content="max-age=0" /> + <meta http-equiv="cache-control" content="no-cache" /> + <meta http-equiv="expires" content="0" /> + <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" /> + <meta http-equiv="pragma" content="no-cache"/> + + <link rel="stylesheet" href="app/fusion/scripts/view-models/reportdashboard-page/src/app/vendor.css"> +<!-- <meta charset="utf-8"> + <title>Malhar Angular Dashboard</title> + <meta name="description" content=""> + <meta name="viewport" content="width=device-width"> --> + <!-- CSS --> + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/fn-ebz.css" > + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/ebz_header/portal_ebz_header.css"> + <link rel="import" href="app/fusion/scripts/view-models/header.html"> + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/sandbox/styles/demo.css" > + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/sandbox/styles/base.css" > + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/sandbox/styles/btn.css" > + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/sandbox/styles/dtpk.css" > + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/sandbox/styles/frms.css" > + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/sandbox/styles/sldr.css" > + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/sandbox/styles/tbs.css" > + <link rel="stylesheet" type="text/css" href="static/fusion/css/jquery-ui.css"> + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/sandbox/styles/style.css" > + <!-- + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/sandbox/styles/style.css" > + --> + <!-- Basic AngularJS --> + + + <!-- build:css({.tmp/serve,src}) styles/vendor.css --> + +<!-- <link rel="stylesheet" href="app/fusion/scripts/view-models/reportdashboard-page/src/app/vendor.css"> --> + <!-- bower:css --> + <!-- endbower --> + <!-- endbuild --> + + <!-- build:css({.tmp/serve,src}) styles/app.css --> + <!-- inject:css --> + <link rel="stylesheet" href="app/fusion/scripts/view-models/reportdashboard-page/src/app/index.css"> + <!-- endinject --> + <!-- endbuild --> + + <!-- build:js(src) scripts/vendor.js --> + <!-- bower:js --> +<!-- <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/jquery/dist/jquery.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/angular/angular.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/jquery-ui/jquery-ui.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/angular-ui-sortable/sortable.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/bootstrap/dist/js/bootstrap.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/lodash/lodash.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/angular-cookies/angular-cookies.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/angular-mocks/angular-mocks.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/angular-route/angular-route.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/angular-sanitize/angular-sanitize.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/showdown/src/showdown.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/bower_components/angular-markdown-directive/markdown.js"></script> + --> + <script src="app/fusion/ase/scripts/dependencies/jquery-2.1.4.min.js"></script> + <script src="app/fusion/external/angular-1.4.8/angular.js"></script> + <script src="app/fusion/external/angular-1.4.8/angular-cookies.js"></script> + <script src="app/fusion/external/angular-1.4.8/angular-mocks.js"></script> + <script src="app/fusion/external/angular-1.4.8/angular-route.js"></script> + <script src="app/fusion/external/angular-1.4.8/angular-sanitize.js"></script> + <script src="app/fusion/external/angular-ui/ui-bootstrap-tpls-1.2.4.min.js"></script> + <script src="app/fusion/ase/scripts/menus/jquery-ui.min.js"></script> + <script src="app/fusion/ase/scripts/menus/bootstrap.min.js"></script> + <script src="app/fusion/external/angular-ui/ui-sortable/v0.13.4/sortable.min.js"></script> + <script src="app/fusion/external/lodash/4.5.1/lodash.js"></script> + <script src="app/fusion/external/showdown/0.3.4/showdown.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/angular-markdown-directive/markdown.js"></script> + + <!-- endbower --> + <!-- endbuild --> + + +<!-- app/fusion/scripts/view-models/reportdashboard-page/ --> + <script src= "app/fusion/external/ebz/angular_js/app.js"></script> + <script src= "app/fusion/external/ebz/sandbox/att-abs-tpls.js" type="text/javascript"></script> + <script src= "app/fusion/external/ebz/angular_js/gestures.js"></script> + <script src="app/fusion/external/angular-ui/ui-bootstrap-tpls-1.1.2.min.js"></script> +<!-- <script src= "app/fusion/external/ebz/angular_js/angular-cookies.js"></script> --> + +<!-- <script src= "app/fusion/external/ebz/angular_js/angular.js"></script> + <script src= "app/fusion/external/ebz/angular_js/angular-sanitize.js"></script> + <script src= "app/fusion/external/ebz/angular_js/angular-route.min.js"></script> + <script src= "app/fusion/external/ebz/angular_js/angular-cookies.js"></script> + <script src= "app/fusion/external/ebz/angular_js/gestures.js"></script> + <script src= "app/fusion/external/ebz/sandbox/att-abs-tpls.js" type="text/javascript"></script> + <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/0.10.0/lodash.min.js"></script> + <script src="app/fusion/external/angular-ui/ui-bootstrap-tpls-1.1.2.min.js"></script> --> + + <!-- jQuery --> +<!-- <script src="static/js/jquery-1.10.2.js"></script> + <script src="static/js/jquery.mask.min.js" type="text/javascript"></script> + <script src="static/js/jquery-ui.js" type="text/javascript"></script> --> + + <!-- AngularJS Gridster --> + <script src="static/fusion/js/att_angular_gridster/ui-gridster-tpls.js"></script> + <script src="static/fusion/js/att_angular_gridster/angular-gridster.js"></script> + <!-- AngularJS Config --> + <script src= "app/fusion/external/ebz/angular_js/checklist-model.js"></script> + <!-- Utility --> + <script src="app/fusion/scripts/modalService.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/app/report_whitelist.js"></script> + <!-- Controller js --> +<!-- <script src="app/fusion/scripts/controllers/fn_menu_add_popup_controller.js"></script> + <script src="app/fusion/scripts/controllers/admin_menu_edit.js"></script> + <script src="app/fusion/scripts/controllers/profile-search-controller.js"></script> + <script src="app/fusion/scripts/controllers/profile-controller.js"></script> + <script src="app/fusion/scripts/controllers/post-search-controller.js"></script> + <script src="app/fusion/scripts/controllers/role-list-controller.js"></script> + <script src="app/fusion/scripts/controllers/role-function-list-controller.js"></script> + <script src="app/fusion/scripts/controllers/rolefunctionpopupController.js"></script> + <script src="app/fusion/scripts/controllers/modelpopupController.js"></script> + <script src="app/fusion/scripts/controllers/jcs-admin-controller.js"></script> + <script src="app/fusion/scripts/controllers/broadcast-list-controller.js"></script> + <script src="app/fusion/scripts/controllers/broadcast-controller.js"></script> + <script src="app/fusion/scripts/controllers/usage-list-controller.js"></script> + <script src="app/fusion/scripts/controllers/collaborate-list-controller.js"></script> + <script src="app/fusion/scripts/controllers/role-controller.js"></script> + <script src="app/fusion/scripts/controllers/rolepopupmodelController.js"></script>--> + <!-- Header and Footer --> + <!-- <script src="app/fusion/external/ebz/js/attHeaderSnippet.js"></script> + <script src="app/fusion/external/ebz/js/attHeader.js"></script> --> + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/ebz_header/header.css"> + <link rel="stylesheet" type="text/css" href="app/fusion/external/ebz/ebz_header/footer.css" > + <script src="app/fusion/scripts/directives/footer.js"></script> + <script src="app/fusion/external/ebz/js/footer.js"></script> + <script src="app/fusion/scripts/directives/header.js"></script> + <script src="app/fusion/scripts/directives/leftMenu.js"></script> + <script src="app/fusion/scripts/services/leftMenuService.js"></script> + + <!-- Services --> + <script src="app/fusion/scripts/services/profileService.js"></script> + <script src="app/fusion/scripts/services/adminService.js"></script> + <script src="app/fusion/scripts/services/userInfoService.js"></script> + <!-- Controller --> +<!-- <script src="app/fusion/scripts/controllers/adminController.js"></script> --> + <!-- other js lib --> + <script type="text/javascript" src="app/fusion/scripts/socket/peerBroadcast.js"></script> + <script src="app/fusion/scripts/moment.min.js"></script> + + <!-- build:js({.tmp/serve,.tmp/partials,src}) scripts/app.js --> + <!-- inject:js --> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/app/demo.js"></script> +<!-- <script src="app/fusion/scripts/view-models/reportdashboard-page/src/templateCacheHtml.js"></script> --> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/widget.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/widget/DashboardWidgetCtrl.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModalCtrl.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/WidgetSettingsCtrl.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/WidgetSettingsRaptorReportCtrl.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetModel.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDefCollection.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/models/WidgetDataModel.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/models/LayoutStorage.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/components/models/DashboardState.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/app/resize.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/app/layouts.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/app/index.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/app/explicitSave.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/app/directives.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/app/dataModel.js"></script> + <script src="app/fusion/scripts/view-models/reportdashboard-page/src/app/customWidgetSettings.js"></script> + <script src="static/fusion/raptor/ebz/dynamicform.js"></script> + <script src="static/fusion/raptor/ebz/multiselect.js"></script> + <script src="static/fusion/raptor/ebz/report_search.js"></script> +<!-- <script src="static/fusion/raptor/ebz/report_run.js"></script> --> + <script src="static/fusion/raptor/ebz/quick_links.js"></script> + + <script src="static/fusion/raptor/uigrid/vfs_fonts.js"></script> + <script src="static/fusion/raptor/uigrid/ui-grid.js"></script> + + <script src="static/fusion/raptor/ebz/report_chart_wizard.js"></script> + + + <script src="app/fusion/scripts/controllers/modelpopupController.js"></script> + + <script src="static/fusion/raptor/ebz/date_time_picker.js"></script> + <script src="static/fusion/raptor/ebz/moment.js"></script> + <link rel="stylesheet" href="static/fusion/raptor/ebz/date_time_picker.css"/> + <link rel="stylesheet" href="static/fusion/raptor/uigrid/ui-grid.css" type="text/css"> + + + <script> + app.requires.push('ui.dashboard'); + </script> + <script> + angular.module('abs').requires.push('quantum', 'ngRoute', 'ui.grid', + 'ui.grid.pagination','ui.grid.resizeColumns', + 'ui.grid.pinning'); + </script> + + <!-- endinject --> + + <!-- inject:partials --> + <!-- angular templates will be automatically converted in js and inserted here --> + <!-- endinject --> + <!-- endbuild --> + + + + + + </head> + <body class="appBody" ng-app="abs"> + <div q-header></div> + <div q-menu class="appLeftMenu"></div> + <div ng-view id="rightContentUiDashboard" style="position:relative; min-height: 1500px; top:-40px; margin-left:210px;"></div> + <div q-footer class="appFooter"></div> + </body> + +</html>
\ No newline at end of file |