├── .bowerrc ├── .gitignore ├── package.json ├── index.js ├── bower.json ├── public ├── timeOptions.html ├── time.js ├── time.less ├── lib │ └── angular-bootstrap │ │ ├── css │ │ ├── btn-group.less │ │ └── carousel.less │ │ └── js │ │ └── carousel.js ├── time.html └── timeController.js ├── README.md └── LICENSE /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/bower_components" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | .DS_Store 3 | public/bower_components 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kibana-time-plugin", 3 | "version": "kibana" 4 | } 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export default function (kibana) { 2 | 3 | return new kibana.Plugin({ 4 | uiExports: { 5 | visTypes: ['plugins/kibana-time-plugin/time'] 6 | } 7 | }); 8 | 9 | }; 10 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kibana-time-plugin", 3 | "description": "kibana plugin", 4 | "authors": [ 5 | "nreese " 6 | ], 7 | "license": "Apache License 2.0", 8 | "homepage": "https://github.com/nreese/kibana-time-plugin", 9 | "private": true, 10 | "dependencies": { 11 | "bootstrap-addons": "https://github.com/nreese/bootstrap-addons/files/851515/bootstrap-addons.zip" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /public/timeOptions.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 |
8 | 12 |
13 |
14 | 18 |
19 |
20 | 24 |
25 |
26 |
27 | 30 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /public/time.js: -------------------------------------------------------------------------------- 1 | import 'ui/angular-bootstrap'; 2 | import 'plugins/kibana-time-plugin/lib/angular-bootstrap/css/btn-group.less'; 3 | import 'plugins/kibana-time-plugin/lib/angular-bootstrap/css/carousel.less'; 4 | import 'plugins/kibana-time-plugin/lib/angular-bootstrap/js/carousel.js'; 5 | import 'plugins/kibana-time-plugin/bower_components/bootstrap-addons/dist/css/bootstrap-addons.css'; 6 | import 'plugins/kibana-time-plugin/bower_components/bootstrap-addons/dist/js/bootstrap-addons.js'; 7 | import 'plugins/kibana-time-plugin/time.less'; 8 | import 'plugins/kibana-time-plugin/timeController'; 9 | import { VisFactoryProvider } from 'ui/vis/vis_factory'; 10 | import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; 11 | import visTemplate from 'plugins/kibana-time-plugin/time.html'; 12 | import optionsTemplate from 'plugins/kibana-time-plugin/timeOptions.html'; 13 | 14 | VisTypesRegistryProvider.register(TimeVisProvider); 15 | 16 | function TimeVisProvider(Private) { 17 | const VisFactory = Private(VisFactoryProvider); 18 | 19 | return VisFactory.createAngularVisualization({ 20 | name: 'time', 21 | title: 'Time widget', 22 | icon: 'clock', 23 | description: 'Add time inputs to your dashboards.', 24 | visConfig: { 25 | template: visTemplate, 26 | defaults: { 27 | enable_quick: true, 28 | enable_relative: true, 29 | enable_absolut: true, 30 | enable_animation: true, 31 | } 32 | }, 33 | editorConfig: { 34 | optionsTemplate: optionsTemplate 35 | }, 36 | requestHandler: 'none', 37 | responseHandler: 'none' 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /public/time.less: -------------------------------------------------------------------------------- 1 | @import (reference) "../../../src/legacy/ui/public/styles/bootstrap/mixins.less"; 2 | 3 | .time-vis { 4 | padding: 0; 5 | width: 100%; 6 | } 7 | 8 | .vis-editor { 9 | 10 | &.vis-type-markdown { 11 | 12 | .visualization-options { 13 | flex: 1 1 auto; 14 | } 15 | 16 | .time-vis-options { 17 | flex: 1 1 auto; 18 | 19 | textarea { 20 | flex: 1 1 auto; 21 | resize: none; 22 | } 23 | 24 | } 25 | 26 | } 27 | 28 | } 29 | 30 | .time-vis { 31 | .btn { 32 | color: #F6F6F6; 33 | } 34 | .btn-xs { 35 | width: 24px; 36 | height: 22px; 37 | } 38 | 39 | .carousel-control { 40 | width: 40px; 41 | } 42 | .carousel { 43 | height: 100%; 44 | } 45 | .carousel-indicators { 46 | position: relative; 47 | top: 85%; 48 | bottom: 0px; 49 | } 50 | } 51 | 52 | .tab-dashboard.theme-light { 53 | .carousel-indicators li { 54 | border: 1px solid black; 55 | } 56 | .carousel-indicators .active { 57 | background-color: black; 58 | } 59 | .ba-timeslider { 60 | stroke: #6EADC1; 61 | fill: #F6F6F6; 62 | } 63 | .ba-scaleline { 64 | stroke: #6EADC1; 65 | } 66 | .ba-scalelabel { 67 | fill: #6EADC1; 68 | font-size: 0.8em; 69 | } 70 | } 71 | 72 | .tab-dashboard.theme-dark { 73 | .time-vis { 74 | .btn { 75 | color: #cecece; 76 | } 77 | .glyphicon { 78 | color: #cecece; 79 | } 80 | } 81 | .carousel-indicators li { 82 | border: 1px solid #cecece; 83 | } 84 | .carousel-indicators .active { 85 | background-color: #cecece; 86 | } 87 | .ba-timeslider { 88 | stroke: #666666; 89 | fill: #444444; 90 | } 91 | .ba-scaleline { 92 | stroke: #cecece; 93 | } 94 | .ba-scalelabel { 95 | fill: #cecece; 96 | font-size: 0.8em; 97 | } 98 | .ba-brush .extent { 99 | stroke: #d9534f; 100 | fill: #d9534f; 101 | fill-opacity: .5; 102 | shape-rendering: crispEdges; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kibana-time-plugin 2 | Widget to view and edit the time range from within dashboards. This plugin started as a work around for issue [Timepicker as part of embedded dashboard](https://github.com/elastic/kibana/issues/2739). 3 | 4 | ## Time Animation 5 | Use the time animation controls to easily explore data by time blocks. Click and drag on the time animation bar to select a time range. Grab the brush to move or resize. Click outside of the brush to remove. Use the step buttons to advance to the next time block. 6 | 7 | ![alt text](https://github.com/nreese/kibana-time-plugin/blob/gh-pages/images/time_animation.gif) 8 | 9 | ## Quick, Relative, and Absolue 10 | Use this widget to view and edit the time range from within dashboards. Use the carousel controls to switch between 'quick', 'relative', 'absolute', and 'time animation' inputs. 11 | 12 | ![alt text](https://github.com/nreese/kibana-time-plugin/blob/gh-pages/images/time.gif) 13 | 14 | # Install 15 | ## Kibana 7.0 16 | ```bash 17 | cd KIBANA_HOME/plugins 18 | git clone git@github.com:nreese/kibana-time-plugin.git (or git clone https://github.com/nreese/kibana-time-plugin.git) 19 | cd KIBANA_HOME/plugins/kibana-time-plugin 20 | 21 | git checkout master 22 | 23 | bower install 24 | vi kibana-time-plugin/package.json //set version to match kibana version 25 | ``` 26 | 27 | # Install 28 | ## Kibana 6.7 29 | ```bash 30 | cd KIBANA_HOME/plugins 31 | git clone git@github.com:nreese/kibana-time-plugin.git (or git clone https://github.com/nreese/kibana-time-plugin.git) 32 | cd KIBANA_HOME/plugins/kibana-time-plugin 33 | 34 | git checkout 6.7 35 | 36 | bower install 37 | vi kibana-time-plugin/package.json //set version to match kibana version 38 | ``` 39 | 40 | ## Kibana 6.6 41 | ```bash 42 | cd KIBANA_HOME/plugins 43 | git clone git@github.com:nreese/kibana-time-plugin.git (or git clone https://github.com/nreese/kibana-time-plugin.git) 44 | cd KIBANA_HOME/plugins/kibana-time-plugin 45 | 46 | git checkout 6.6 47 | 48 | bower install 49 | vi kibana-time-plugin/package.json //set version to match kibana version 50 | ``` 51 | 52 | # Install 53 | ## Kibana 6.4 and 6.5 54 | ```bash 55 | cd KIBANA_HOME/plugins 56 | git clone git@github.com:nreese/kibana-time-plugin.git (or git clone https://github.com/nreese/kibana-time-plugin.git) 57 | cd KIBANA_HOME/plugins/kibana-time-plugin 58 | 59 | git checkout 6.5 60 | 61 | bower install 62 | vi kibana-time-plugin/package.json //set version to match kibana version 63 | ``` 64 | 65 | ## Kibana 6.3 66 | ```bash 67 | cd KIBANA_HOME/plugins 68 | git clone git@github.com:nreese/kibana-time-plugin.git (or git clone https://github.com/nreese/kibana-time-plugin.git) 69 | cd KIBANA_HOME/plugins/kibana-time-plugin 70 | 71 | git checkout 6.3 72 | 73 | bower install 74 | vi kibana-time-plugin/package.json //set version to match kibana version 75 | ``` 76 | 77 | ## Kibana 6.2 78 | ```bash 79 | cd KIBANA_HOME/plugins 80 | git clone git@github.com:nreese/kibana-time-plugin.git (or git clone https://github.com/nreese/kibana-time-plugin.git) 81 | cd KIBANA_HOME/plugins/kibana-time-plugin 82 | 83 | git checkout 6.2 84 | 85 | bower install 86 | vi kibana-time-plugin/package.json //set version to match kibana version 87 | ``` 88 | 89 | ## Kibana 6.0 - 6.1 90 | ```bash 91 | cd KIBANA_HOME/plugins 92 | git clone git@github.com:nreese/kibana-time-plugin.git (or git clone https://github.com/nreese/kibana-time-plugin.git) 93 | cd KIBANA_HOME/plugins/kibana-time-plugin 94 | 95 | git checkout 6.0 96 | 97 | bower install 98 | vi kibana-time-plugin/package.json //set version to match kibana version 99 | ``` 100 | 101 | ## Kibana 5.5 - 5.6 102 | ```bash 103 | cd KIBANA_HOME/plugins 104 | git clone git@github.com:nreese/kibana-time-plugin.git (or git clone https://github.com/nreese/kibana-time-plugin.git) 105 | cd KIBANA_HOME/plugins/kibana-time-plugin 106 | 107 | git checkout 5.5 108 | 109 | bower install 110 | vi kibana-time-plugin/package.json //set version to match kibana version 111 | ``` 112 | 113 | ## Kibana 5.0 - 5.4 114 | ```bash 115 | cd KIBANA_HOME/plugins 116 | git clone git@github.com:nreese/kibana-time-plugin.git (or git clone https://github.com/nreese/kibana-time-plugin.git) 117 | cd KIBANA_HOME/plugins/kibana-time-plugin 118 | 119 | git checkout 5.4 120 | 121 | bower install 122 | vi kibana-time-plugin/package.json //set version to match kibana version 123 | ``` 124 | 125 | ## Kibana 4.x 126 | ```bash 127 | ./bin/kibana plugin -i kibana-time-plugin -u https://github.com/nreese/kibana-time-plugin/archive/4.x.zip 128 | ``` 129 | 130 | # Uninstall 131 | ## Kibana 5.x 132 | ```bash 133 | ./bin/kibana-plugin remove kibana-time-plugin 134 | ``` 135 | -------------------------------------------------------------------------------- /public/lib/angular-bootstrap/css/btn-group.less: -------------------------------------------------------------------------------- 1 | .time-vis { 2 | .btn-group, 3 | .btn-group-vertical { 4 | position: relative; 5 | display: inline-block; 6 | vertical-align: middle; 7 | } 8 | .btn-group > .btn, 9 | .btn-group-vertical > .btn { 10 | position: relative; 11 | float: left; 12 | } 13 | .btn-group > .btn:hover, 14 | .btn-group-vertical > .btn:hover, 15 | .btn-group > .btn:focus, 16 | .btn-group-vertical > .btn:focus, 17 | .btn-group > .btn:active, 18 | .btn-group-vertical > .btn:active, 19 | .btn-group > .btn.active, 20 | .btn-group-vertical > .btn.active { 21 | z-index: 2; 22 | } 23 | .btn-group .btn + .btn, 24 | .btn-group .btn + .btn-group, 25 | .btn-group .btn-group + .btn, 26 | .btn-group .btn-group + .btn-group { 27 | margin-left: -1px; 28 | } 29 | 30 | .btn-toolbar .btn, 31 | .btn-toolbar .btn-group, 32 | .btn-toolbar .input-group { 33 | float: left; 34 | } 35 | .btn-toolbar > .btn, 36 | .btn-toolbar > .btn-group, 37 | .btn-toolbar > .input-group { 38 | margin-left: 5px; 39 | } 40 | .btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { 41 | border-radius: 0; 42 | } 43 | .btn-group > .btn:first-child { 44 | margin-left: 0; 45 | } 46 | .btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { 47 | border-top-right-radius: 0; 48 | border-bottom-right-radius: 0; 49 | } 50 | .btn-group > .btn:last-child:not(:first-child), 51 | .btn-group > .dropdown-toggle:not(:first-child) { 52 | border-top-left-radius: 0; 53 | border-bottom-left-radius: 0; 54 | } 55 | .btn-group > .btn-group { 56 | float: left; 57 | } 58 | .btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { 59 | border-radius: 0; 60 | } 61 | .btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, 62 | .btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { 63 | border-top-right-radius: 0; 64 | border-bottom-right-radius: 0; 65 | } 66 | .btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { 67 | border-top-left-radius: 0; 68 | border-bottom-left-radius: 0; 69 | } 70 | .btn-group .dropdown-toggle:active, 71 | .btn-group.open .dropdown-toggle { 72 | outline: 0; 73 | } 74 | .btn-group > .btn + .dropdown-toggle { 75 | padding-right: 8px; 76 | padding-left: 8px; 77 | } 78 | .btn-group > .btn-lg + .dropdown-toggle { 79 | padding-right: 12px; 80 | padding-left: 12px; 81 | } 82 | .btn-group.open .dropdown-toggle { 83 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 84 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 85 | } 86 | .btn-group.open .dropdown-toggle.btn-link { 87 | -webkit-box-shadow: none; 88 | box-shadow: none; 89 | } 90 | 91 | .btn-group-vertical > .btn, 92 | .btn-group-vertical > .btn-group, 93 | .btn-group-vertical > .btn-group > .btn { 94 | display: block; 95 | float: none; 96 | width: 100%; 97 | max-width: 100%; 98 | } 99 | .btn-group-vertical > .btn-group > .btn { 100 | float: none; 101 | } 102 | .btn-group-vertical > .btn + .btn, 103 | .btn-group-vertical > .btn + .btn-group, 104 | .btn-group-vertical > .btn-group + .btn, 105 | .btn-group-vertical > .btn-group + .btn-group { 106 | margin-top: -1px; 107 | margin-left: 0; 108 | } 109 | .btn-group-vertical > .btn:not(:first-child):not(:last-child) { 110 | border-radius: 0; 111 | } 112 | .btn-group-vertical > .btn:first-child:not(:last-child) { 113 | border-top-left-radius: 4px; 114 | border-top-right-radius: 4px; 115 | border-bottom-right-radius: 0; 116 | border-bottom-left-radius: 0; 117 | } 118 | .btn-group-vertical > .btn:last-child:not(:first-child) { 119 | border-top-left-radius: 0; 120 | border-top-right-radius: 0; 121 | border-bottom-right-radius: 4px; 122 | border-bottom-left-radius: 4px; 123 | } 124 | .btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { 125 | border-radius: 0; 126 | } 127 | .btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, 128 | .btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { 129 | border-bottom-right-radius: 0; 130 | border-bottom-left-radius: 0; 131 | } 132 | .btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { 133 | border-top-left-radius: 0; 134 | border-top-right-radius: 0; 135 | } 136 | .btn-group-justified { 137 | display: table; 138 | width: 100%; 139 | table-layout: fixed; 140 | border-collapse: separate; 141 | } 142 | .btn-group-justified > .btn, 143 | .btn-group-justified > .btn-group { 144 | display: table-cell; 145 | float: none; 146 | width: 1%; 147 | } 148 | .btn-group-justified > .btn-group .btn { 149 | width: 100%; 150 | } 151 | .btn-group-justified > .btn-group .dropdown-menu { 152 | left: auto; 153 | } 154 | [data-toggle="buttons"] > .btn input[type="radio"], 155 | [data-toggle="buttons"] > .btn-group > .btn input[type="radio"], 156 | [data-toggle="buttons"] > .btn input[type="checkbox"], 157 | [data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { 158 | position: absolute; 159 | clip: rect(0, 0, 0, 0); 160 | pointer-events: none; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /public/time.html: -------------------------------------------------------------------------------- 1 |
2 |

{{config.title}}

3 | 4 | 5 | 6 |

7 | Quick 8 |

9 |
10 | 12 |
13 |
14 | 15 | 16 |

17 | Relative 18 |

19 |
20 |
21 | 26 |
27 |
28 | 35 |
36 |
37 | 43 |
44 |
45 |
46 | 54 |
55 |
56 | 57 |
58 | 61 |
62 |
63 | 64 |
65 |
66 | 67 |
68 | 69 |
70 |
71 | 74 |
75 |
76 |
77 |
78 | 79 | 80 |

81 | Absolute 82 |

83 |
84 |
85 | 87 | 88 |
89 | 90 |
91 | 93 | 94 |
95 | 96 |
97 | 100 | From must occur before To 101 |
102 |
103 |
104 | 105 | 106 |

107 | Time Animation 108 | 109 |

110 |

111 | {{animationTitle}} 112 |

113 | 114 |
115 | 118 | 124 |
125 |
126 |
127 | 128 | 129 |
130 | -------------------------------------------------------------------------------- /public/timeController.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import dateMath from '@elastic/datemath'; 3 | import moment from 'moment'; 4 | import { SimpleEmitter } from 'ui/utils/simple_emitter'; 5 | import { timefilter } from 'ui/timefilter'; 6 | import { uiModules } from 'ui/modules'; 7 | const module = uiModules.get('kibana/kibana-time-plugin', ['kibana', 'ktp-ui.bootstrap.carousel', 'BootstrapAddons']); 8 | 9 | const msearchEmitter = new SimpleEmitter(); 10 | module.config(function($httpProvider) { 11 | $httpProvider.interceptors.push(function() { 12 | return { 13 | response: function(resp) { 14 | if (resp.config.url.includes('_msearch')) { 15 | msearchEmitter.emit('msearch:response'); 16 | } 17 | return resp; 18 | } 19 | } 20 | }); 21 | }); 22 | 23 | module.controller('KbnTimeVisController', function (config, $scope, $rootScope, Private, $filter, $timeout) { 24 | const TIMESLIDER_INSTR = "Click and drag to select a time range." 25 | const DATE_FORMAT = 'MMMM Do YYYY, HH:mm:ss z'; 26 | $rootScope.plugin = { 27 | timePlugin: {} 28 | }; 29 | 30 | let lastUpdated = 0; 31 | 32 | //$scope.$listenAndDigestAsync(timefilter, 'timeUpdate', setTime); 33 | 34 | $scope.$listen(timefilter, 'timeUpdate', () => { 35 | $scope.$evalAsync(() => setTime()); 36 | }); 37 | 38 | var changeVisOff = $rootScope.$on( 39 | 'change:vis', 40 | _.debounce(updateTimeslider, 200, false)); 41 | $scope.$on('$destroy', function() { 42 | changeVisOff(); 43 | }); 44 | 45 | var expectedFrom = moment(); 46 | var expectedTo = moment(); 47 | $scope.animationTitle = TIMESLIDER_INSTR; 48 | 49 | const quickRanges = config.get('timepicker:quickRanges'); 50 | $scope.quickLists = quickRanges; 51 | 52 | $scope.units = { 53 | s: 'second', 54 | m: 'minute', 55 | h: 'hour', 56 | d: 'day', 57 | w: 'week', 58 | M: 'month', 59 | y: 'year' 60 | }; 61 | $scope.relativeOptions = [ 62 | {text: 'Seconds ago', value: 's'}, 63 | {text: 'Minutes ago', value: 'm'}, 64 | {text: 'Hours ago', value: 'h'}, 65 | {text: 'Days ago', value: 'd'}, 66 | {text: 'Weeks ago', value: 'w'}, 67 | {text: 'Months ago', value: 'M'}, 68 | {text: 'Years ago', value: 'y'}, 69 | ]; 70 | $scope.relative = { 71 | count: 1, 72 | unit: 'm', 73 | preview: undefined, 74 | round: false 75 | }; 76 | $scope.sliderRoundOptions = [ 77 | {text: 'Second', value: 's'}, 78 | {text: 'Minute', value: 'm'}, 79 | {text: 'Hour', value: 'h'}, 80 | {text: 'Day', value: 'd'}, 81 | {text: 'Week', value: 'w'}, 82 | {text: 'Month', value: 'M'}, 83 | {text: 'Year', value: 'y'}, 84 | ]; 85 | $scope.slider = { 86 | roundUnit: 's', 87 | }; 88 | 89 | //custom playback that waits for kibana msearch response before advancing timeframe 90 | let nextStep = null; 91 | $scope.kibanaPlayback = { 92 | play: function(nextCallback) { 93 | nextCallback(); 94 | const delay = _.get($scope.vis.params, 'animation_frame_delay', 1) * 1000; 95 | nextStep = _.debounce(nextCallback, delay); 96 | }, 97 | pause: function() { 98 | if (nextStep) { 99 | nextStep.cancel(); 100 | nextStep = null; 101 | } 102 | } 103 | } 104 | msearchEmitter.on('msearch:response', function() { 105 | if (nextStep) { 106 | nextStep(); 107 | } 108 | }); 109 | 110 | //When timeslider carousel slide is not displayed, it has a width of 0 111 | //attach click handler to carousel controls to redraw 112 | $timeout(function() { 113 | var elems = document.getElementsByClassName('carousel-control'); 114 | for (var i=0; i 200) { 134 | console.log("updating KbnTimeVisController.$scope stay in sync with kibana timefilter"); 135 | const newTime = timefilter.getTime(); 136 | 137 | //clean up old selections 138 | $scope.activeSlide = { 139 | absolute: false, 140 | quick: false, 141 | relative: false 142 | }; 143 | 144 | //set new selections based on new time 145 | $scope.time = { 146 | from: newTime.from, 147 | to: newTime.to, 148 | absolute_from: dateMath.parse(newTime.from), 149 | absolute_to: dateMath.parse(newTime.to, true) 150 | } 151 | setRelativeParts(newTime.to, newTime.from); 152 | if('quick' === newTime.mode) { 153 | $scope.activeSlide.quick = true; 154 | for(var i=0; i 1) { 265 | $scope.relative.count = Math.round(as); 266 | $scope.relative.unit = units[i]; 267 | break; 268 | } 269 | } 270 | } 271 | 272 | if (from.toString().split('/')[1]) $scope.relative.round = true; 273 | formatRelative(); 274 | } 275 | function formatRelative() { 276 | var parsed = dateMath.parse(getRelativeString()); 277 | $scope.relative.preview = parsed ? parsed.format($scope.format) : undefined; 278 | return parsed; 279 | } 280 | $scope.formatRelative = formatRelative; 281 | 282 | function getRelativeString() { 283 | return 'now-' + $scope.relative.count + $scope.relative.unit + ($scope.relative.round ? '/' + $scope.relative.unit : ''); 284 | } 285 | 286 | // avoid digest cycle overflow by cachine start Date 287 | let lastStart; 288 | $scope.getStartTime = function() { 289 | const start = $scope.time.absolute_from.toDate(); 290 | if (lastStart && lastStart.getTime() === start.getTime()) { 291 | return lastStart; 292 | } 293 | 294 | lastStart = start; 295 | return start; 296 | } 297 | 298 | // avoid digest cycle overflow by cachine end Date 299 | let lastEnd; 300 | $scope.getEndTime = function() { 301 | const end = $scope.time.absolute_to.toDate(); 302 | if (lastEnd && lastEnd.getTime() === end.getTime()) { 303 | return lastEnd; 304 | } 305 | 306 | lastEnd = end; 307 | return end; 308 | } 309 | 310 | $scope.renderComplete(); 311 | }); 312 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /public/lib/angular-bootstrap/css/carousel.less: -------------------------------------------------------------------------------- 1 | .time-vis { 2 | 3 | /*! 4 | * Bootstrap v3.3.7 (http://getbootstrap.com) 5 | * Copyright 2011-2016 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | */ 8 | 9 | /*! 10 | * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=b76f55817cf128185542c0690815ff32) 11 | * Config saved to config.json and https://gist.github.com/b76f55817cf128185542c0690815ff32 12 | */ 13 | /*! 14 | * Bootstrap v3.3.7 (http://getbootstrap.com) 15 | * Copyright 2011-2016 Twitter, Inc. 16 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 17 | */ 18 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ 19 | html { 20 | font-family: sans-serif; 21 | -ms-text-size-adjust: 100%; 22 | -webkit-text-size-adjust: 100%; 23 | } 24 | body { 25 | margin: 0; 26 | } 27 | article, 28 | aside, 29 | details, 30 | figcaption, 31 | figure, 32 | footer, 33 | header, 34 | hgroup, 35 | main, 36 | menu, 37 | nav, 38 | section, 39 | summary { 40 | display: block; 41 | } 42 | audio, 43 | canvas, 44 | progress, 45 | video { 46 | display: inline-block; 47 | vertical-align: baseline; 48 | } 49 | audio:not([controls]) { 50 | display: none; 51 | height: 0; 52 | } 53 | [hidden], 54 | template { 55 | display: none; 56 | } 57 | a { 58 | background-color: transparent; 59 | } 60 | a:active, 61 | a:hover { 62 | outline: 0; 63 | } 64 | abbr[title] { 65 | border-bottom: 1px dotted; 66 | } 67 | b, 68 | strong { 69 | font-weight: bold; 70 | } 71 | dfn { 72 | font-style: italic; 73 | } 74 | h1 { 75 | font-size: 2em; 76 | margin: 0.67em 0; 77 | } 78 | mark { 79 | background: #ff0; 80 | color: #000; 81 | } 82 | small { 83 | font-size: 80%; 84 | } 85 | sub, 86 | sup { 87 | font-size: 75%; 88 | line-height: 0; 89 | position: relative; 90 | vertical-align: baseline; 91 | } 92 | sup { 93 | top: -0.5em; 94 | } 95 | sub { 96 | bottom: -0.25em; 97 | } 98 | img { 99 | border: 0; 100 | } 101 | svg:not(:root) { 102 | overflow: hidden; 103 | } 104 | figure { 105 | margin: 1em 40px; 106 | } 107 | hr { 108 | -webkit-box-sizing: content-box; 109 | -moz-box-sizing: content-box; 110 | box-sizing: content-box; 111 | height: 0; 112 | } 113 | pre { 114 | overflow: auto; 115 | } 116 | code, 117 | kbd, 118 | pre, 119 | samp { 120 | font-family: monospace, monospace; 121 | font-size: 1em; 122 | } 123 | button, 124 | input, 125 | optgroup, 126 | select, 127 | textarea { 128 | color: inherit; 129 | font: inherit; 130 | margin: 0; 131 | } 132 | button { 133 | overflow: visible; 134 | } 135 | button, 136 | select { 137 | text-transform: none; 138 | } 139 | button, 140 | html input[type="button"], 141 | input[type="reset"], 142 | input[type="submit"] { 143 | -webkit-appearance: button; 144 | cursor: pointer; 145 | } 146 | button[disabled], 147 | html input[disabled] { 148 | cursor: default; 149 | } 150 | button::-moz-focus-inner, 151 | input::-moz-focus-inner { 152 | border: 0; 153 | padding: 0; 154 | } 155 | input { 156 | line-height: normal; 157 | } 158 | input[type="checkbox"], 159 | input[type="radio"] { 160 | -webkit-box-sizing: border-box; 161 | -moz-box-sizing: border-box; 162 | box-sizing: border-box; 163 | padding: 0; 164 | } 165 | input[type="number"]::-webkit-inner-spin-button, 166 | input[type="number"]::-webkit-outer-spin-button { 167 | height: auto; 168 | } 169 | input[type="search"] { 170 | -webkit-appearance: textfield; 171 | -webkit-box-sizing: content-box; 172 | -moz-box-sizing: content-box; 173 | box-sizing: content-box; 174 | } 175 | input[type="search"]::-webkit-search-cancel-button, 176 | input[type="search"]::-webkit-search-decoration { 177 | -webkit-appearance: none; 178 | } 179 | fieldset { 180 | border: 1px solid #c0c0c0; 181 | margin: 0 2px; 182 | padding: 0.35em 0.625em 0.75em; 183 | } 184 | legend { 185 | border: 0; 186 | padding: 0; 187 | } 188 | textarea { 189 | overflow: auto; 190 | } 191 | optgroup { 192 | font-weight: bold; 193 | } 194 | table { 195 | border-collapse: collapse; 196 | border-spacing: 0; 197 | } 198 | td, 199 | th { 200 | padding: 0; 201 | } 202 | * { 203 | -webkit-box-sizing: border-box; 204 | -moz-box-sizing: border-box; 205 | box-sizing: border-box; 206 | } 207 | *:before, 208 | *:after { 209 | -webkit-box-sizing: border-box; 210 | -moz-box-sizing: border-box; 211 | box-sizing: border-box; 212 | } 213 | html { 214 | font-size: 10px; 215 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 216 | } 217 | body { 218 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 219 | font-size: 14px; 220 | line-height: 1.42857143; 221 | color: #333333; 222 | background-color: #ffffff; 223 | } 224 | input, 225 | button, 226 | select, 227 | textarea { 228 | font-family: inherit; 229 | font-size: inherit; 230 | line-height: inherit; 231 | } 232 | a { 233 | color: #337ab7; 234 | text-decoration: none; 235 | } 236 | a:hover, 237 | a:focus { 238 | color: #23527c; 239 | text-decoration: underline; 240 | } 241 | a:focus { 242 | outline: 5px auto -webkit-focus-ring-color; 243 | outline-offset: -2px; 244 | } 245 | figure { 246 | margin: 0; 247 | } 248 | img { 249 | vertical-align: middle; 250 | } 251 | .img-responsive, 252 | .carousel-inner > .item > img, 253 | .carousel-inner > .item > a > img { 254 | display: block; 255 | max-width: 100%; 256 | height: auto; 257 | } 258 | .img-rounded { 259 | border-radius: 6px; 260 | } 261 | .img-thumbnail { 262 | padding: 4px; 263 | line-height: 1.42857143; 264 | background-color: #ffffff; 265 | border: 1px solid #dddddd; 266 | border-radius: 4px; 267 | -webkit-transition: all 0.2s ease-in-out; 268 | -o-transition: all 0.2s ease-in-out; 269 | transition: all 0.2s ease-in-out; 270 | display: inline-block; 271 | max-width: 100%; 272 | height: auto; 273 | } 274 | .img-circle { 275 | border-radius: 50%; 276 | } 277 | hr { 278 | margin-top: 20px; 279 | margin-bottom: 20px; 280 | border: 0; 281 | border-top: 1px solid #eeeeee; 282 | } 283 | .sr-only { 284 | position: absolute; 285 | width: 1px; 286 | height: 1px; 287 | margin: -1px; 288 | padding: 0; 289 | overflow: hidden; 290 | clip: rect(0, 0, 0, 0); 291 | border: 0; 292 | } 293 | .sr-only-focusable:active, 294 | .sr-only-focusable:focus { 295 | position: static; 296 | width: auto; 297 | height: auto; 298 | margin: 0; 299 | overflow: visible; 300 | clip: auto; 301 | } 302 | [role="button"] { 303 | cursor: pointer; 304 | } 305 | .carousel { 306 | position: relative; 307 | } 308 | .carousel-inner { 309 | position: relative; 310 | overflow: hidden; 311 | width: 100%; 312 | } 313 | .carousel-inner > .item { 314 | display: none; 315 | position: relative; 316 | -webkit-transition: 0.6s ease-in-out left; 317 | -o-transition: 0.6s ease-in-out left; 318 | transition: 0.6s ease-in-out left; 319 | } 320 | .carousel-inner > .item > img, 321 | .carousel-inner > .item > a > img { 322 | line-height: 1; 323 | } 324 | @media all and (transform-3d), (-webkit-transform-3d) { 325 | .carousel-inner > .item { 326 | -webkit-transition: -webkit-transform 0.6s ease-in-out; 327 | -o-transition: -o-transform 0.6s ease-in-out; 328 | transition: transform 0.6s ease-in-out; 329 | -webkit-backface-visibility: hidden; 330 | backface-visibility: hidden; 331 | -webkit-perspective: 1000px; 332 | perspective: 1000px; 333 | } 334 | .carousel-inner > .item.next, 335 | .carousel-inner > .item.active.right { 336 | -webkit-transform: translate3d(100%, 0, 0); 337 | transform: translate3d(100%, 0, 0); 338 | left: 0; 339 | } 340 | .carousel-inner > .item.prev, 341 | .carousel-inner > .item.active.left { 342 | -webkit-transform: translate3d(-100%, 0, 0); 343 | transform: translate3d(-100%, 0, 0); 344 | left: 0; 345 | } 346 | .carousel-inner > .item.next.left, 347 | .carousel-inner > .item.prev.right, 348 | .carousel-inner > .item.active { 349 | -webkit-transform: translate3d(0, 0, 0); 350 | transform: translate3d(0, 0, 0); 351 | left: 0; 352 | } 353 | } 354 | .carousel-inner > .active, 355 | .carousel-inner > .next, 356 | .carousel-inner > .prev { 357 | display: block; 358 | } 359 | .carousel-inner > .active { 360 | left: 0; 361 | } 362 | .carousel-inner > .next, 363 | .carousel-inner > .prev { 364 | position: absolute; 365 | top: 0; 366 | width: 100%; 367 | } 368 | .carousel-inner > .next { 369 | left: 100%; 370 | } 371 | .carousel-inner > .prev { 372 | left: -100%; 373 | } 374 | .carousel-inner > .next.left, 375 | .carousel-inner > .prev.right { 376 | left: 0; 377 | } 378 | .carousel-inner > .active.left { 379 | left: -100%; 380 | } 381 | .carousel-inner > .active.right { 382 | left: 100%; 383 | } 384 | .carousel-control { 385 | position: absolute; 386 | top: 0; 387 | left: 0; 388 | bottom: 0; 389 | width: 15%; 390 | opacity: 0.5; 391 | filter: alpha(opacity=50); 392 | font-size: 20px; 393 | color: #ffffff; 394 | text-align: center; 395 | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); 396 | background-color: rgba(0, 0, 0, 0); 397 | } 398 | .carousel-control.left { 399 | background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); 400 | background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); 401 | background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001))); 402 | background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); 403 | background-repeat: repeat-x; 404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); 405 | } 406 | .carousel-control.right { 407 | left: auto; 408 | right: 0; 409 | background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); 410 | background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); 411 | background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5))); 412 | background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); 413 | background-repeat: repeat-x; 414 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); 415 | } 416 | .carousel-control:hover, 417 | .carousel-control:focus { 418 | outline: 0; 419 | color: #ffffff; 420 | text-decoration: none; 421 | opacity: 0.9; 422 | filter: alpha(opacity=90); 423 | } 424 | .carousel-control .icon-prev, 425 | .carousel-control .icon-next, 426 | .carousel-control .glyphicon-chevron-left, 427 | .carousel-control .glyphicon-chevron-right { 428 | position: absolute; 429 | top: 50%; 430 | margin-top: -10px; 431 | z-index: 5; 432 | display: inline-block; 433 | } 434 | .carousel-control .icon-prev, 435 | .carousel-control .glyphicon-chevron-left { 436 | left: 50%; 437 | margin-left: -10px; 438 | } 439 | .carousel-control .icon-next, 440 | .carousel-control .glyphicon-chevron-right { 441 | right: 50%; 442 | margin-right: -10px; 443 | } 444 | .carousel-control .icon-prev, 445 | .carousel-control .icon-next { 446 | width: 20px; 447 | height: 20px; 448 | line-height: 1; 449 | font-family: serif; 450 | } 451 | .carousel-control .icon-prev:before { 452 | content: '\2039'; 453 | } 454 | .carousel-control .icon-next:before { 455 | content: '\203a'; 456 | } 457 | .carousel-indicators { 458 | position: absolute; 459 | bottom: 10px; 460 | left: 50%; 461 | z-index: 15; 462 | width: 60%; 463 | margin-left: -30%; 464 | padding-left: 0; 465 | list-style: none; 466 | text-align: center; 467 | } 468 | .carousel-indicators li { 469 | display: inline-block; 470 | width: 10px; 471 | height: 10px; 472 | margin: 1px; 473 | text-indent: -999px; 474 | border: 1px solid #ffffff; 475 | border-radius: 10px; 476 | cursor: pointer; 477 | background-color: #000 \9; 478 | background-color: rgba(0, 0, 0, 0); 479 | } 480 | .carousel-indicators .active { 481 | margin: 0; 482 | width: 12px; 483 | height: 12px; 484 | background-color: #ffffff; 485 | } 486 | .carousel-caption { 487 | position: absolute; 488 | left: 15%; 489 | right: 15%; 490 | bottom: 20px; 491 | z-index: 10; 492 | padding-top: 20px; 493 | padding-bottom: 20px; 494 | color: #ffffff; 495 | text-align: center; 496 | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); 497 | } 498 | .carousel-caption .btn { 499 | text-shadow: none; 500 | } 501 | @media screen and (min-width: 768px) { 502 | .carousel-control .glyphicon-chevron-left, 503 | .carousel-control .glyphicon-chevron-right, 504 | .carousel-control .icon-prev, 505 | .carousel-control .icon-next { 506 | width: 30px; 507 | height: 30px; 508 | margin-top: -10px; 509 | font-size: 30px; 510 | } 511 | .carousel-control .glyphicon-chevron-left, 512 | .carousel-control .icon-prev { 513 | margin-left: -10px; 514 | } 515 | .carousel-control .glyphicon-chevron-right, 516 | .carousel-control .icon-next { 517 | margin-right: -10px; 518 | } 519 | .carousel-caption { 520 | left: 20%; 521 | right: 20%; 522 | padding-bottom: 30px; 523 | } 524 | .carousel-indicators { 525 | bottom: 20px; 526 | } 527 | } 528 | .clearfix:before, 529 | .clearfix:after { 530 | content: " "; 531 | display: table; 532 | } 533 | .clearfix:after { 534 | clear: both; 535 | } 536 | .center-block { 537 | display: block; 538 | margin-left: auto; 539 | margin-right: auto; 540 | } 541 | .pull-right { 542 | float: right !important; 543 | } 544 | .pull-left { 545 | float: left !important; 546 | } 547 | .hide { 548 | display: none !important; 549 | } 550 | .show { 551 | display: block !important; 552 | } 553 | .invisible { 554 | visibility: hidden; 555 | } 556 | .text-hide { 557 | font: 0/0 a; 558 | color: transparent; 559 | text-shadow: none; 560 | background-color: transparent; 561 | border: 0; 562 | } 563 | .hidden { 564 | display: none !important; 565 | } 566 | .affix { 567 | position: fixed; 568 | } 569 | 570 | } 571 | -------------------------------------------------------------------------------- /public/lib/angular-bootstrap/js/carousel.js: -------------------------------------------------------------------------------- 1 | /* 2 | * angular-ui-bootstrap 3 | * http://angular-ui.github.io/bootstrap/ 4 | 5 | * Version: 0.12.1 - 2015-10-17 6 | * License: MIT 7 | */ 8 | angular.module("ktp-ui.bootstrap", ["ktp-ui.bootstrap.tpls","ktp-ui.bootstrap.carousel","ktp-ui.bootstrap.transition"]); 9 | angular.module("ktp-ui.bootstrap.tpls", ["template/carousel/carousel.html","template/carousel/slide.html"]); 10 | /** 11 | * @ngdoc overview 12 | * @name ktp-ui.bootstrap.carousel 13 | * 14 | * @description 15 | * AngularJS version of an image carousel. 16 | * 17 | */ 18 | angular.module('ktp-ui.bootstrap.carousel', ['ktp-ui.bootstrap.transition']) 19 | .controller('CarouselController', ['$scope', '$timeout', '$interval', '$transition', function ($scope, $timeout, $interval, $transition) { 20 | var self = this, 21 | slides = self.slides = $scope.slides = [], 22 | currentIndex = -1, 23 | currentInterval, isPlaying; 24 | self.currentSlide = null; 25 | 26 | var destroyed = false; 27 | /* direction: "prev" or "next" */ 28 | self.select = $scope.select = function(nextSlide, direction) { 29 | var nextIndex = slides.indexOf(nextSlide); 30 | //Decide direction if it's not given 31 | if (direction === undefined) { 32 | direction = nextIndex > currentIndex ? 'next' : 'prev'; 33 | } 34 | if (nextSlide && nextSlide !== self.currentSlide) { 35 | if ($scope.$currentTransition) { 36 | $scope.$currentTransition.cancel(); 37 | //Timeout so ng-class in template has time to fix classes for finished slide 38 | $timeout(goNext); 39 | } else { 40 | goNext(); 41 | } 42 | } 43 | function goNext() { 44 | // Scope has been destroyed, stop here. 45 | if (destroyed) { return; } 46 | //If we have a slide to transition from and we have a transition type and we're allowed, go 47 | if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { 48 | //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime 49 | nextSlide.$element.addClass(direction); 50 | var reflow = nextSlide.$element[0].offsetWidth; //force reflow 51 | 52 | //Set all other slides to stop doing their stuff for the new transition 53 | angular.forEach(slides, function(slide) { 54 | angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); 55 | }); 56 | angular.extend(nextSlide, {direction: direction, active: true, entering: true}); 57 | angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); 58 | 59 | $scope.$currentTransition = $transition(nextSlide.$element, {}); 60 | //We have to create new pointers inside a closure since next & current will change 61 | (function(next,current) { 62 | $scope.$currentTransition.then( 63 | function(){ transitionDone(next, current); }, 64 | function(){ transitionDone(next, current); } 65 | ); 66 | }(nextSlide, self.currentSlide)); 67 | } else { 68 | transitionDone(nextSlide, self.currentSlide); 69 | } 70 | self.currentSlide = nextSlide; 71 | currentIndex = nextIndex; 72 | //every time you change slides, reset the timer 73 | restartTimer(); 74 | } 75 | function transitionDone(next, current) { 76 | angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); 77 | angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); 78 | $scope.$currentTransition = null; 79 | } 80 | }; 81 | $scope.$on('$destroy', function () { 82 | destroyed = true; 83 | }); 84 | 85 | /* Allow outside people to call indexOf on slides array */ 86 | self.indexOfSlide = function(slide) { 87 | return slides.indexOf(slide); 88 | }; 89 | 90 | $scope.next = function() { 91 | var newIndex = (currentIndex + 1) % slides.length; 92 | 93 | //Prevent this user-triggered transition from occurring if there is already one in progress 94 | if (!$scope.$currentTransition) { 95 | return self.select(slides[newIndex], 'next'); 96 | } 97 | }; 98 | 99 | $scope.prev = function() { 100 | var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; 101 | 102 | //Prevent this user-triggered transition from occurring if there is already one in progress 103 | if (!$scope.$currentTransition) { 104 | return self.select(slides[newIndex], 'prev'); 105 | } 106 | }; 107 | 108 | $scope.isActive = function(slide) { 109 | return self.currentSlide === slide; 110 | }; 111 | 112 | $scope.$watch('interval', restartTimer); 113 | $scope.$on('$destroy', resetTimer); 114 | 115 | function restartTimer() { 116 | resetTimer(); 117 | var interval = +$scope.interval; 118 | if (!isNaN(interval) && interval > 0) { 119 | currentInterval = $interval(timerFn, interval); 120 | } 121 | } 122 | 123 | function resetTimer() { 124 | if (currentInterval) { 125 | $interval.cancel(currentInterval); 126 | currentInterval = null; 127 | } 128 | } 129 | 130 | function timerFn() { 131 | var interval = +$scope.interval; 132 | if (isPlaying && !isNaN(interval) && interval > 0) { 133 | $scope.next(); 134 | } else { 135 | $scope.pause(); 136 | } 137 | } 138 | 139 | $scope.play = function() { 140 | if (!isPlaying) { 141 | isPlaying = true; 142 | restartTimer(); 143 | } 144 | }; 145 | $scope.pause = function() { 146 | if (!$scope.noPause) { 147 | isPlaying = false; 148 | resetTimer(); 149 | } 150 | }; 151 | 152 | self.addSlide = function(slide, element) { 153 | slide.$element = element; 154 | slides.push(slide); 155 | //if this is the first slide or the slide is set to active, select it 156 | if(slides.length === 1 || slide.active) { 157 | self.select(slides[slides.length-1]); 158 | if (slides.length == 1) { 159 | $scope.play(); 160 | } 161 | } else { 162 | slide.active = false; 163 | } 164 | }; 165 | 166 | self.removeSlide = function(slide) { 167 | //get the index of the slide inside the carousel 168 | var index = slides.indexOf(slide); 169 | slides.splice(index, 1); 170 | if (slides.length > 0 && slide.active) { 171 | if (index >= slides.length) { 172 | self.select(slides[index-1]); 173 | } else { 174 | self.select(slides[index]); 175 | } 176 | } else if (currentIndex > index) { 177 | currentIndex--; 178 | } 179 | }; 180 | 181 | }]) 182 | 183 | /** 184 | * @ngdoc directive 185 | * @name ktp-ui.bootstrap.carousel.directive:carousel 186 | * @restrict EA 187 | * 188 | * @description 189 | * Carousel is the outer container for a set of image 'slides' to showcase. 190 | * 191 | * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. 192 | * @param {boolean=} noTransition Whether to disable transitions on the carousel. 193 | * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). 194 | * 195 | * @example 196 | 197 | 198 | 199 | 200 | 201 | 204 | 205 | 206 | 207 | 210 | 211 | 212 | 213 | 214 | .carousel-indicators { 215 | top: auto; 216 | bottom: 15px; 217 | } 218 | 219 | 220 | */ 221 | .directive('carousel', [function() { 222 | return { 223 | restrict: 'EA', 224 | transclude: true, 225 | replace: true, 226 | controller: 'CarouselController', 227 | require: 'carousel', 228 | template: "
\n" + 229 | "
    1\">\n" + 230 | "
  1. \n" + 231 | "
\n" + 232 | "
\n" + 233 | " 1\">\n" + 234 | " 1\">\n" + 235 | "
\n" + 236 | "", 237 | scope: { 238 | interval: '=', 239 | noTransition: '=', 240 | noPause: '=' 241 | } 242 | }; 243 | }]) 244 | 245 | /** 246 | * @ngdoc directive 247 | * @name ktp-ui.bootstrap.carousel.directive:slide 248 | * @restrict EA 249 | * 250 | * @description 251 | * Creates a slide inside a {@link ktp-ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. 252 | * 253 | * @param {boolean=} active Model binding, whether or not this slide is currently active. 254 | * 255 | * @example 256 | 257 | 258 |
259 | 260 | 261 | 262 | 266 | 267 | 268 | Interval, in milliseconds: 269 |
Enter a negative number to stop the interval. 270 |
271 |
272 | 273 | function CarouselDemoCtrl($scope) { 274 | $scope.myInterval = 5000; 275 | } 276 | 277 | 278 | .carousel-indicators { 279 | top: auto; 280 | bottom: 15px; 281 | } 282 | 283 |
284 | */ 285 | 286 | .directive('slide', function() { 287 | return { 288 | require: '^carousel', 289 | restrict: 'EA', 290 | transclude: true, 291 | replace: true, 292 | template: "
\n" + 299 | "", 300 | scope: { 301 | active: '=?' 302 | }, 303 | link: function (scope, element, attrs, carouselCtrl) { 304 | carouselCtrl.addSlide(scope, element); 305 | //when the scope is destroyed then remove the slide from the current slides array 306 | scope.$on('$destroy', function() { 307 | carouselCtrl.removeSlide(scope); 308 | }); 309 | 310 | scope.$watch('active', function(active) { 311 | if (active) { 312 | carouselCtrl.select(scope); 313 | } 314 | }); 315 | } 316 | }; 317 | }); 318 | 319 | angular.module('ktp-ui.bootstrap.transition', []) 320 | 321 | /** 322 | * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. 323 | * @param {DOMElement} element The DOMElement that will be animated. 324 | * @param {string|object|function} trigger The thing that will cause the transition to start: 325 | * - As a string, it represents the css class to be added to the element. 326 | * - As an object, it represents a hash of style attributes to be applied to the element. 327 | * - As a function, it represents a function to be called that will cause the transition to occur. 328 | * @return {Promise} A promise that is resolved when the transition finishes. 329 | */ 330 | .factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { 331 | 332 | var $transition = function(element, trigger, options) { 333 | options = options || {}; 334 | var deferred = $q.defer(); 335 | var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName']; 336 | 337 | var transitionEndHandler = function(event) { 338 | $rootScope.$apply(function() { 339 | element.unbind(endEventName, transitionEndHandler); 340 | deferred.resolve(element); 341 | }); 342 | }; 343 | 344 | if (endEventName) { 345 | element.bind(endEventName, transitionEndHandler); 346 | } 347 | 348 | // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur 349 | $timeout(function() { 350 | if ( angular.isString(trigger) ) { 351 | element.addClass(trigger); 352 | } else if ( angular.isFunction(trigger) ) { 353 | trigger(element); 354 | } else if ( angular.isObject(trigger) ) { 355 | element.css(trigger); 356 | } 357 | //If browser does not support transitions, instantly resolve 358 | if ( !endEventName ) { 359 | deferred.resolve(element); 360 | } 361 | }); 362 | 363 | // Add our custom cancel function to the promise that is returned 364 | // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, 365 | // i.e. it will therefore never raise a transitionEnd event for that transition 366 | deferred.promise.cancel = function() { 367 | if ( endEventName ) { 368 | element.unbind(endEventName, transitionEndHandler); 369 | } 370 | deferred.reject('Transition cancelled'); 371 | }; 372 | 373 | return deferred.promise; 374 | }; 375 | 376 | // Work out the name of the transitionEnd event 377 | var transElement = document.createElement('trans'); 378 | var transitionEndEventNames = { 379 | 'WebkitTransition': 'webkitTransitionEnd', 380 | 'MozTransition': 'transitionend', 381 | 'OTransition': 'oTransitionEnd', 382 | 'transition': 'transitionend' 383 | }; 384 | var animationEndEventNames = { 385 | 'WebkitTransition': 'webkitAnimationEnd', 386 | 'MozTransition': 'animationend', 387 | 'OTransition': 'oAnimationEnd', 388 | 'transition': 'animationend' 389 | }; 390 | function findEndEventName(endEventNames) { 391 | for (var name in endEventNames){ 392 | if (transElement.style[name] !== undefined) { 393 | return endEventNames[name]; 394 | } 395 | } 396 | } 397 | $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); 398 | $transition.animationEndEventName = findEndEventName(animationEndEventNames); 399 | return $transition; 400 | }]); 401 | --------------------------------------------------------------------------------