summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPliable Pixels <pliablepixels@users.noreply.github.com>2016-10-03 14:14:55 -0400
committerGitHub <noreply@github.com>2016-10-03 14:14:55 -0400
commite69b00e63f06bcccafc713a46fc31847d598d419 (patch)
tree94582a32a960488c2b3b0bc26577bc1c44e0d91a
parenta1a8d2e24f638ffe5bcf7229fcc84271aa145282 (diff)
parent8a8b1d9766735becc351643697ea9f495767ab6a (diff)
Merge pull request #340 from pliablepixels/shrinking-header
initial experiments Former-commit-id: 5194ee61e6690f8ddbdbba3e9dd14a116c9bd929
-rw-r--r--.jscodehints1
-rw-r--r--.jshintrc1
-rwxr-xr-xbuild_android.sh3
-rw-r--r--config.xml3
-rw-r--r--package.json16
-rw-r--r--plugins/fetch.json28
-rw-r--r--www/css/style.css15
-rw-r--r--www/external/ionic.headerShrink.js73
-rw-r--r--www/external/ionic.scroll.sista.js318
-rw-r--r--www/external/packery.pkgd.js14
-rw-r--r--www/index.html7
-rw-r--r--www/js/DataModel.js8
-rw-r--r--www/js/EventServer.js29
-rw-r--r--www/js/MontageHistoryCtrl2.js1708
-rw-r--r--www/js/app.js8
-rw-r--r--www/lib/ionic-content-banner/.bower.json10
-rw-r--r--www/lib/ionic-content-banner/index994
-rw-r--r--www/templates/devoptions.html2
-rw-r--r--www/templates/events-date-time-filter.html2
-rw-r--r--www/templates/events.html11
-rw-r--r--www/templates/eventserversettings.html2
-rw-r--r--www/templates/help.html4
-rw-r--r--www/templates/log.html2
-rw-r--r--www/templates/login.html2
-rw-r--r--www/templates/monitors.html4
-rw-r--r--www/templates/montage-history.html2
-rw-r--r--www/templates/montage-history2.html230
-rw-r--r--www/templates/montage.html2
-rw-r--r--www/templates/news.html2
-rw-r--r--www/templates/state.html2
-rw-r--r--www/templates/timeline.html2
-rw-r--r--www/templates/wizard.html1
32 files changed, 3449 insertions, 57 deletions
diff --git a/.jscodehints b/.jscodehints
new file mode 100644
index 00000000..b6a3d3f5
--- /dev/null
+++ b/.jscodehints
@@ -0,0 +1 @@
+{ "excluded-directories" : ["merges", "platforms", "www/lib", "node_modules"] }
diff --git a/.jshintrc b/.jshintrc
index 74966837..70702fe8 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -1 +1,2 @@
/*jshint devel:false */
+
diff --git a/build_android.sh b/build_android.sh
index b9daded9..43182a9a 100755
--- a/build_android.sh
+++ b/build_android.sh
@@ -91,6 +91,9 @@ if [ "$BUILD_MODE" = "native" ] || [ "$BUILD_MODE" = "all" ]; then
~/Library/Android/sdk/build-tools/22.0.1/zipalign -v 4 android-release-unsigned.apk zmNinja.apk
rm -f android-release-unsigned.apk
cd ..
+ ./checkperms.sh release_files/zmNinja.apk
+ echo "*** Phone State Check:"
+ ./checkperms.sh release_files/zmNinja.apk | grep PHONE_STATE
fi
diff --git a/config.xml b/config.xml
index ed7b13ff..237d1b96 100644
--- a/config.xml
+++ b/config.xml
@@ -15,6 +15,7 @@
<preference name="CrosswalkAnimatable" value="true" />
<preference name="webviewbounce" value="false" />
<preference name="UIWebViewBounce" value="false" />
+ <preference name="StatusBarBackgroundColor" value="#2980b9" />
<preference name="DisallowOverscroll" value="true" />
<preference name="BackupWebStorage" value="none" />
<preference name="AutoHideSplashScreen" value="false" />
@@ -24,7 +25,7 @@
<preference name="SplashScreenDelay" value="300" />
<preference name="SplashMaintainAspectRatio" value="true" />
<preference name="FadeSplashScreen" value="false" />
- <preference name="BackgroundColor" value="0xff555555" />
+ <preference name="BackgroundColor" value="#444444" />
<feature name="StatusBar">
<param name="ios-package" onload="true" value="CDVStatusBar" />
</feature>
diff --git a/package.json b/package.json
index 448df406..08a00eea 100644
--- a/package.json
+++ b/package.json
@@ -67,11 +67,11 @@
"locator": "https://github.com/jcjee/email-composer",
"id": "com.jcjee.plugins.emailcomposer"
},
- "cordova-plugin-compat",
{
"locator": "https://github.com/pliablepixels/cordova-plugin-media",
- "id": "cordova-plugin-media"
+ "id": "org.apache.cordova.media"
},
+ "cordova-plugin-compat",
{
"locator": "https://github.com/litehelpers/Cordova-sqlite-storage.git",
"id": "cordova-sqlite-storage"
@@ -80,11 +80,19 @@
"cordova-plugin-android-permissions",
"cordova-plugin-network-information",
"cordova-plugin-statusbar",
+ "cordova-plugin-settings-hook",
+ {
+ "locator": "https://github.com/phonegap/phonegap-mobile-accessibility.git",
+ "id": "phonegap-plugin-mobile-accessibility"
+ },
+ {
+ "locator": "https://github.com/apache/cordova-plugin-device.git",
+ "id": "cordova-plugin-device"
+ },
{
"locator": "https://github.com/hypery2k/cordova-certificate-plugin",
"id": "cordova-plugin-certificates"
- },
- "cordova-plugin-settings-hook"
+ }
],
"cordovaPlatforms": [
{},
diff --git a/plugins/fetch.json b/plugins/fetch.json
index 27ba9870..8fcf245c 100644
--- a/plugins/fetch.json
+++ b/plugins/fetch.json
@@ -134,30 +134,11 @@
"is_top_level": true,
"variables": {}
},
- "org.apache.cordova.media": {
- "source": {
- "type": "git",
- "url": "https://github.com/pliablepixels/cordova-plugin-media",
- "subdir": ".",
- "ref": "playback-only"
- },
- "is_top_level": true,
- "variables": {}
- },
"cordova-plugin-compat": {
"source": {
"type": "registry",
"id": "cordova-plugin-compat"
},
- "is_top_level": false,
- "variables": {}
- },
- "cordova-plugin-media": {
- "source": {
- "type": "git",
- "url": "https://github.com/pliablepixels/cordova-plugin-media",
- "subdir": "."
- },
"is_top_level": true,
"variables": {}
},
@@ -228,6 +209,15 @@
"is_top_level": false,
"variables": {}
},
+ "cordova-plugin-media": {
+ "source": {
+ "type": "git",
+ "url": "https://github.com/pliablepixels/cordova-plugin-media",
+ "subdir": "."
+ },
+ "is_top_level": true,
+ "variables": {}
+ },
"cordova-plugin-certificates": {
"source": {
"type": "git",
diff --git a/www/css/style.css b/www/css/style.css
index 33e6f972..cc61aee5 100644
--- a/www/css/style.css
+++ b/www/css/style.css
@@ -99,12 +99,12 @@ Credit: https://css-tricks.com/snippets/css/a-guide-to-flexbox/
opacity: 80%;
}
.header-event-id {
- background: #9b59b6;
- color: #cccccc;
- border-color: #9b59b6;
- border-style: solid;
- border-width: 1px;
- opacity: 80%;
+ background:rgba(52, 152, 219,0.2);
+ color: #FFF;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ opacity: 1;
}
.header-paused {
background: #ba3e3e;
@@ -912,4 +912,5 @@ body {
}
.white-button-text {
color: #fff !important;
-} \ No newline at end of file
+}
+
diff --git a/www/external/ionic.headerShrink.js b/www/external/ionic.headerShrink.js
new file mode 100644
index 00000000..70f9a1d2
--- /dev/null
+++ b/www/external/ionic.headerShrink.js
@@ -0,0 +1,73 @@
+angular.module('ionic.ion.headerShrink', [])
+
+ .directive('headerShrink', function ($document) {
+ var fadeAmt;
+
+ var shrink = function (header, content, amt, max) {
+ amt = Math.min(max, amt);
+ fadeAmt = 1 - amt / max;
+ ionic.requestAnimationFrame(function () {
+ header.style[ionic.CSS.TRANSFORM] = 'translate3d(0, -' + amt + 'px, 0)';
+ for (var i = 0, j = header.children.length; i < j; i++) {
+ header.children[i].style.opacity = fadeAmt;
+ }
+ });
+ };
+
+ return {
+ restrict: 'A',
+ link: function ($scope, $element, $attr) {
+ var starty = $scope.$eval($attr.headerShrink) || 0;
+ var shrinkAmt;
+
+ var amt;
+
+ var y = 0;
+ var prevY = 0;
+ var scrollDelay = 0.4;
+
+ var fadeAmt;
+
+ var headers = $document[0].body.querySelectorAll('.bar-header');
+ var headerHeight = headers[0].offsetHeight;
+
+ function onScroll(e) {
+ var scrollTop = e.detail.scrollTop;
+
+ if (scrollTop >= 0) {
+ y = Math.min(headerHeight / scrollDelay, Math.max(0, y + scrollTop - prevY));
+ } else {
+ y = 0;
+ }
+ //console.log(scrollTop);
+
+ ionic.requestAnimationFrame(function () {
+ fadeAmt = 1 - (y / headerHeight);
+ for (var k = 0, l = headers.length; k < l; k++) {
+ headers[k].style[ionic.CSS.TRANSFORM] = 'translate3d(0, ' + -y + 'px, 0)';
+ headers[k].style.opacity = fadeAmt;
+ }
+ });
+
+ prevY = scrollTop;
+ }
+
+ $element.bind('scroll', onScroll);
+ }
+ }
+})
+
+angular.module('app', ['ionic', 'ionic.ion.headerShrink'])
+ .config(function ($stateProvider, $urlRouterProvider) {
+
+ // Define our views
+ $stateProvider.state('home', {
+ url: "/home",
+ templateUrl: 'views/home.html'
+ // If you wanted some AngularJS controller behaviour...
+ // controller: "HomeCtrl as ctrl"
+ });
+
+ // Default view to show
+ $urlRouterProvider.otherwise('/home');
+});
diff --git a/www/external/ionic.scroll.sista.js b/www/external/ionic.scroll.sista.js
new file mode 100644
index 00000000..598c7ca7
--- /dev/null
+++ b/www/external/ionic.scroll.sista.js
@@ -0,0 +1,318 @@
+/* global angular,ionic */
+(function (angular, ionic) {
+ 'use strict';
+
+ angular.module('jett.ionic.scroll.sista', ['ionic'])
+ .directive('scrollSista', ['$document', '$timeout', '$rootScope', '$ionicScrollDelegate', '$ionicPlatform',function($document, $timeout, $rootScope, $ionicScrollDelegate, $ionicPlatform) {
+ var TRANSITION_DELAY = 400;
+ var defaultDelay = TRANSITION_DELAY * 2;
+ var defaultDuration = TRANSITION_DELAY + 'ms';
+ var scaleHeaderElements = ionic.Platform.isAndroid() ? false : true;
+
+ function getParentWithAttr (e, attrName, attrValue, depth) {
+ var attr;
+
+ depth = depth || 10;
+ while (e.parentNode && depth--) {
+ attr = e.parentNode.getAttribute(attrName);
+ if (attr && attr === attrValue) {
+ return e.parentNode;
+ }
+ e = e.parentNode;
+ }
+ return null;
+ }
+
+ return {
+ restrict: 'A',
+ link: function($scope, $element, $attr) {
+ var isNavBarTransitioning = true;
+ var body = $document[0].body;
+ var scrollDelegate = $attr.delegateHandle ? $ionicScrollDelegate.$getByHandle($attr.delegateHandle) : $ionicScrollDelegate;
+ var scrollView = scrollDelegate.getScrollView();
+ var isIos = false;
+
+ //coordinates
+ var y, prevY, prevScrollTop;
+ //headers
+ var cachedHeader, activeHeader, headerHeight, contentTop;
+ //subheader
+ var subHeader, subHeaderHeight;
+ //tabs
+ var tabs, tabsHeight, hasTabsTop = false, hasTabsBottom = false;
+
+ //y position that will indicate where specific elements should start and end their transition.
+ var headerStart = 0;
+ var tabsStart = 0;
+ var subheaderStart = 0;
+ var defaultEnd, headerEnd, tabsEnd, subheaderEnd;
+
+ /**
+ * translates an element along the y axis by the supplied value. if duration is passed in,
+ * a transition duration is set
+ * @param element
+ * @param y
+ * @param duration
+ */
+ function translateY (element, y, duration) {
+ if (duration && !element.style[ionic.CSS.TRANSITION_DURATION]) {
+ element.style[ionic.CSS.TRANSITION_DURATION] = duration;
+ $timeout(function () {
+ element.style[ionic.CSS.TRANSITION_DURATION] = '';
+ }, defaultDelay, false);
+ }
+ element.style[ionic.CSS.TRANSFORM] = 'translate3d(0, ' + (-y) + 'px, 0)';
+ }
+
+ /**
+ * Initializes y and scroll variables
+ */
+ function initCoordinates () {
+ y = 0;
+ prevY = 0;
+ prevScrollTop = 0;
+ }
+
+ /**
+ * Initializes headers, tabs, and subheaders, and determines how they will transition on scroll
+ */
+ function init () {
+ var activeView;
+
+ cachedHeader = body.querySelector('[nav-bar="cached"] .bar-header');
+ activeHeader = body.querySelector('[nav-bar="active"] .bar-header');
+
+ if (!activeHeader) {
+ return;
+ }
+
+ // credit - adapted 20px PR from https://github.com/driftyco/ionic-ion-header-shrink/pull/10
+
+ headerHeight = activeHeader.offsetHeight;
+ $ionicPlatform.ready(function() {
+ if($ionicPlatform.is('ios')) {
+ isIos = true;
+ headerHeight -= 20; // account 20px for the ios status bar
+ }
+ });
+ contentTop = headerHeight;
+
+ //since some people can have nested tabs, get the last tabs
+ tabs = body.querySelectorAll('.tabs');
+ tabs = tabs[tabs.length - 1];
+ if (tabs) {
+ tabsHeight = tabs.offsetHeight;
+ if (tabs.parentNode.classList.contains('tabs-top')) {
+ hasTabsTop = true;
+ contentTop += tabsHeight;
+ } else if (tabs.parentNode.classList.contains('tabs-bottom')) {
+ hasTabsBottom = true;
+ }
+ }
+
+ //subheader
+ //since subheader is going to be nested in the active view, get the closest active view from $element and
+ activeView = getParentWithAttr($element[0], 'nav-view', 'active');
+ subHeader = activeView && activeView.querySelector('.bar-subheader');
+ if (subHeader) {
+ subHeaderHeight = subHeader.offsetHeight;
+ contentTop += subHeaderHeight;
+ }
+
+ //set default end for header/tabs elements to scroll out of the scroll view and set elements end to default
+ defaultEnd = contentTop * 2;
+ headerEnd = tabsEnd = subheaderEnd = defaultEnd;
+
+ //if tabs or subheader aren't available, set height to 0
+ tabsHeight = tabsHeight || 0;
+ subHeaderHeight = subHeaderHeight || 0;
+
+ switch($attr.scrollSista) {
+ case 'header':
+ subheaderEnd = headerHeight;
+ tabsEnd = hasTabsTop ? headerHeight : 0;
+ break;
+ case 'header-tabs':
+ headerStart = hasTabsTop ? tabsHeight : 0;
+ subheaderEnd = hasTabsTop ? headerHeight + tabsHeight : headerHeight;
+ break;
+ case 'tabs-subheader':
+ headerEnd = 0;
+ headerStart = hasTabsTop ? contentTop - headerHeight : subHeaderHeight;
+ tabsStart = hasTabsTop ? subHeaderHeight : 0;
+ break;
+ case 'tabs':
+ headerEnd = 0;
+ subheaderEnd = hasTabsTop ? tabsHeight : 0;
+ break;
+ case 'subheader':
+ headerEnd = 0;
+ tabsEnd = 0;
+ break;
+ case 'header-subheader':
+ tabsEnd = hasTabsTop ? headerHeight : 0;
+ break;
+ case 'subheader-header':
+ headerStart = subHeaderHeight;
+ tabsStart = hasTabsTop ? subHeaderHeight : 0;
+ tabsEnd = hasTabsTop ? headerHeight : 0;
+ break;
+ //defaults to header-tabs-subheader
+ default:
+ headerStart = hasTabsTop ? contentTop - headerHeight : subHeaderHeight;
+ tabsStart = hasTabsTop ? subHeaderHeight : 0;
+ }
+ }
+
+ /**
+ * Translates active and cached headers, and animates active children
+ * @param y
+ * @param duration
+ */
+ function translateHeaders (y, duration) {
+ var fadeAmt = Math.max(0, 1 - (y / headerHeight));
+
+ //translate active header
+ if (activeHeader) {
+ translateY(activeHeader, y, duration);
+ angular.forEach(activeHeader.children, function (child) {
+ child.style.opacity = fadeAmt;
+ if (scaleHeaderElements) {
+ child.style[ionic.CSS.TRANSFORM] = 'scale(' + fadeAmt + ',' + fadeAmt + ')';
+ }
+ });
+ }
+
+ //translate cached header
+ if (cachedHeader) {
+ translateY(cachedHeader, y, duration);
+ }
+ }
+
+ /**
+ * Translates header, tabs, subheader elements and resets content top and/or bottom
+ * When the active view leaves, we need sync functionality to reset headers and clear
+ * @param y
+ * @param duration
+ */
+ function translateElementsSync (y, duration) {
+ var contentStyle = $element[0].style;
+ var headerY = y > headerStart ? y - headerStart : 0;
+ var tabsY, subheaderY;
+
+ //subheader
+ if (subHeader) {
+ subheaderY = y > subheaderStart ? y - subheaderStart : 0;
+ translateY(subHeader, Math.min(subheaderEnd, subheaderY), duration);
+ }
+
+ //tabs
+ if (tabs) {
+ tabsY = Math.min(tabsEnd, y > tabsStart ? y - tabsStart : 0);
+
+ if (hasTabsBottom) {
+ tabsY = -tabsY;
+ contentStyle.bottom = Math.max(0, tabsHeight - y) + 'px';
+ }
+ translateY(tabs, tabsY, duration);
+ }
+
+ //headers
+ translateHeaders(Math.min(headerEnd, headerY), duration);
+
+ //readjust top of ion-content
+ contentStyle.top = Math.max(0, contentTop - y) + 'px';
+ }
+
+ /**
+ * Translates header, tabs, subheader elements and resets content top and/or bottom
+ * Wraps translate functionality in an animation frame request
+ * @param y
+ * @param duration
+ */
+ function translateElements (y, duration) {
+ ionic.requestAnimationFrame(function() {
+ translateElementsSync(y, duration);
+ });
+ }
+
+ //Need to reinitialize the values on refreshComplete or things will get out of wack
+ $scope.$on('scroll.refreshComplete', function () {
+ initCoordinates();
+ });
+
+ /**
+ * Before the active view leaves, reset elements, and reset the scroll container
+ */
+ $scope.$parent.$on('$ionicView.beforeLeave', function () {
+ isNavBarTransitioning = true;
+ translateElementsSync(0);
+ activeHeader = null;
+ cachedHeader = null;
+ });
+
+ /**
+ * Scroll to the top when entering to reset then scrollView scrollTop. (prevents jumping)
+ */
+ $scope.$parent.$on('$ionicView.beforeEnter', function () {
+ if (scrollView) {
+ scrollView.scrollTo(0, 0);
+ }
+ });
+
+ /**
+ * Ionic sets the active/cached nav-bar AFTER the afterEnter event is called, so we need to set a small
+ * timeout to let the nav-bar logic run.
+ */
+ $scope.$parent.$on('$ionicView.afterEnter', function () {
+ initCoordinates();
+
+ $timeout(function () {
+ init();
+ isNavBarTransitioning = false;
+ }, 20, false);
+ });
+
+ /**
+ * Called onScroll. computes coordinates based on scroll position and translates accordingly
+ */
+ $element.bind('scroll', function (e) {
+ if (isNavBarTransitioning) {
+ return;
+ }
+ //support for jQuery event as well
+ e = e.originalEvent || e;
+
+ var duration = 0;
+ var scrollTop = e.detail?e.detail.scrollTop:e.target.scrollTop;
+
+ y = scrollTop >= 0 ? Math.min(defaultEnd, Math.max(0, y + scrollTop - prevScrollTop)) : 0;
+ if (isIos && y >headerHeight)
+ {
+ y = headerHeight;
+ }
+
+
+ //if we are at the bottom, animate the header/tabs back in
+ if (scrollView.getScrollMax().top - scrollTop <= contentTop) {
+ // console.log ("MAX TOP="+scrollView.getScrollMax().top+" SCROLL TOP:"+scrollTop+" CONTOP="+contentTop);
+ // y = 0;
+ // duration = defaultDuration;
+ }
+
+ prevScrollTop = scrollTop;
+
+ //if previous and current y are the same, no need to continue
+ if (prevY === y) {
+ return;
+ }
+ prevY = y;
+
+ translateElements(y, duration);
+ });
+
+ }
+ }
+ }]);
+
+})(angular, ionic);
diff --git a/www/external/packery.pkgd.js b/www/external/packery.pkgd.js
index a85a10f4..66c3b21f 100644
--- a/www/external/packery.pkgd.js
+++ b/www/external/packery.pkgd.js
@@ -2758,6 +2758,20 @@ Packery.prototype.getShiftPositions = function( attrName ) {
});
};
+// get JSON-friendly data for items positions
+Packery.prototype.EHgetShiftPositions = function( attrName ) {
+ attrName = attrName || 'id';
+ var _this = this;
+ return this.items.map( function( item ) {
+ return {
+ attr: item.element.getAttribute( attrName ),
+ size: item.element.getAttribute ("eh-data-item-size"),
+ display: item.element.getAttribute ("eh-data-item-listdisplay"),
+ x: item.rect.x / _this.packer.width,
+ y: item.rect.y / _this.packer.height
+ }
+ });
+};
Packery.prototype.initShiftLayout = function( positions, attr ) {
if ( !positions ) {
// if no initial positions, run packery layout
diff --git a/www/index.html b/www/index.html
index 66f2c178..6b7ffa55 100644
--- a/www/index.html
+++ b/www/index.html
@@ -104,7 +104,7 @@
<script src="js/EventServer.js"></script>
<script src="js/EventServerSettingsCtrl.js"></script>
<script src="js/FirstUseCtrl.js"></script>
- <script src="js/MontageHistoryCtrl.js"></script>
+ <script src="js/MontageHistoryCtrl2.js"></script>
<script src="js/NewsCtrl.js"></script>
<script src="js/TimelineModalCtrl.js"></script>
<script src="js/WizardCtrl.js"></script>
@@ -144,10 +144,13 @@
<script src="external/ionic.content.banner.js"></script>
<script src="external/FileSaver.min.js"></script>
<script src="external/canvas-toBlob.js"></script>
+
<script src="external/imagesloaded.pkgd.js"></script>
<script src="external/packery.pkgd.js"></script>
<script src="external/draggabilly.pkgd.js"></script>
+
+ <script src="external/ionic.scroll.sista.js"></script>
<!-- <script src="external/imagesloaded.pkgd.js"></script>-->
@@ -185,7 +188,7 @@
</ion-side-menu-content>
<ion-side-menu>
- <ion-header-bar class="bar bar-header bar-dark">
+ <ion-header-bar class="bar bar-header bar-stable">
<h1 class="title">{{'kMenuOptions'|translate}}</h1>
</ion-header-bar>
diff --git a/www/js/DataModel.js b/www/js/DataModel.js
index 16b33431..65c5835d 100644
--- a/www/js/DataModel.js
+++ b/www/js/DataModel.js
@@ -105,6 +105,7 @@ angular.module('zmApp.controllers')
'enableBlog': true,
'use24hr': false,
'packeryPositions': '',
+ 'EHpackeryPositions': '',
'packerySizes': '',
'timelineModalGraphType': 'all',
'resumeDelay': 0,
@@ -666,6 +667,13 @@ angular.module('zmApp.controllers')
loginData.packeryPositions = "";
}
+
+
+ if (typeof loginData.EHpackeryPositions == 'undefined') {
+ debug("EHpackeryPositions does not exist. Setting to empty");
+ loginData.EHpackeryPositions = "";
+
+ }
if (typeof loginData.packerySizes == 'undefined') {
diff --git a/www/js/EventServer.js b/www/js/EventServer.js
index 04a3d72a..8ea63531 100644
--- a/www/js/EventServer.js
+++ b/www/js/EventServer.js
@@ -10,16 +10,14 @@
//--------------------------------------------------------------------------
angular.module('zmApp.controllers')
-
.factory('EventServer', ['NVRDataModel', '$rootScope', '$websocket', '$ionicPopup', '$timeout', '$q', 'zm', '$ionicPlatform', '$cordovaMedia', '$translate', function
(NVRDataModel, $rootScope, $websocket, $ionicPopup, $timeout, $q, zm, $ionicPlatform, $cordovaMedia, $translate) {
-
+ var lastEventServerCheck = Date.now();
var ws;
var localNotificationId = 0;
-
-
+ var firstError = true;
//--------------------------------------------------------------------------
@@ -62,6 +60,9 @@ angular.module('zmApp.controllers')
// Called once at app start. Does a lazy definition of websockets open
//--------------------------------------------------------------------------
function init() {
+
+
+
$rootScope.isAlarm = 0;
$rootScope.alarmCount = "0";
@@ -94,7 +95,7 @@ angular.module('zmApp.controllers')
ws = $websocket.$new({
url: loginData.eventServer,
reconnect: true,
- reconnectInterval: 5000,
+ reconnectInterval: 60000,
lazy: true
});
@@ -103,6 +104,24 @@ angular.module('zmApp.controllers')
// Transmit auth information to server
ws.$on('$open', openHandshake);
+ NVRDataModel.debug ("Setting up websocket error handler" );
+ ws.$on('$error', function (e){
+
+ // we don't need this check as I changed reconnect interval to 60s
+ //if ((Date.now() - lastEventServerCheck > 30000.0) || firstError)
+ if (1)
+ {
+ NVRDataModel.debug ("Websocket Errorhandler called");
+ $timeout( function(){
+ NVRDataModel.displayBanner('error',['Event Server connection error']);
+ },3000); // leave 3 seconds for transitions
+ firstError = false;
+ lastEventServerCheck = Date.now();
+ }
+ //console.log ("VALUE TIME " + lastEventServerCheck);
+ //console.log ("NOW TIME " + Date.now());
+ });
+
ws.$on('$close', function () {
NVRDataModel.log("Websocket closed");
diff --git a/www/js/MontageHistoryCtrl2.js b/www/js/MontageHistoryCtrl2.js
new file mode 100644
index 00000000..d9107a0f
--- /dev/null
+++ b/www/js/MontageHistoryCtrl2.js
@@ -0,0 +1,1708 @@
+// Controller for the montage view
+/* jshint -W041 */
+/* jslint browser: true*/
+/* global cordova,StatusBar,angular,console,ionic,Masonry,moment,Packery, Draggabilly, imagesLoaded */
+
+
+// FIXME: This is a copy of montageCtrl - needs a lot of code cleanup
+
+angular.module('zmApp.controllers').controller('zmApp.MontageHistoryCtrl', ['$scope', '$rootScope', 'NVRDataModel', 'message', '$ionicSideMenuDelegate', '$timeout', '$interval', '$ionicModal', '$ionicLoading', '$http', '$state', '$ionicPopup', '$stateParams', '$ionicHistory', '$ionicScrollDelegate', '$ionicPlatform', 'zm', '$ionicPopover', '$controller', 'imageLoadingDataShare', '$window', '$translate', function ($scope, $rootScope, NVRDataModel, message, $ionicSideMenuDelegate, $timeout, $interval, $ionicModal, $ionicLoading, $http, $state, $ionicPopup, $stateParams, $ionicHistory, $ionicScrollDelegate, $ionicPlatform, zm, $ionicPopover, $controller, imageLoadingDataShare, $window, $translate) {
+
+
+
+
+ //--------------------------------------------------------------------------------------
+ // Handles bandwidth change, if required
+ //
+ //--------------------------------------------------------------------------------------
+
+ $rootScope.$on("bandwidth-change", function (e, data) {
+ // nothing to do for now
+ // eventUrl will use lower BW in next query cycle
+ });
+
+
+
+ //--------------------------------------
+ // formats events dates in a nice way
+ //---------------------------------------
+
+ $scope.prettifyDateTimeFirst = function (str) {
+ return moment(str).format(NVRDataModel.getTimeFormat() + '/MMM Do');
+
+ };
+ $scope.prettifyDate = function (str) {
+ return moment(str).format('MMM Do, YYYY ' + NVRDataModel.getTimeFormat());
+ };
+
+ function prettifyDate(str) {
+ return moment(str).format('MMM Do');
+ }
+
+ $scope.prettifyTime = function (str) {
+
+ return moment(str).format('h:mm a');
+ };
+
+
+ $scope.prettify = function (str) {
+ return moment(str).format(NVRDataModel.getTimeFormat() + ' on MMMM Do YYYY');
+ };
+
+
+
+
+ function orientationChanged() {
+ NVRDataModel.debug("Detected orientation change, redoing packery resize");
+ $timeout(function () {
+ pckry.onresize();
+ });
+ }
+
+ $scope.toggleSizeButtons = function () {
+
+ $scope.showSizeButtons = !$scope.showSizeButtons;
+
+ NVRDataModel.debug("toggling size buttons:" + $scope.showSizeButtons);
+ if ($scope.showSizeButtons) $ionicScrollDelegate.$getByHandle("montage-delegate").scrollTop();
+ };
+
+ //--------------------------------------
+ // pause/unpause nph-zms
+ //---------------------------------------
+ $scope.togglePause = function (mid) {
+ //console.log ("TOGGLE PAUSE " + mid);
+ var m = -1;
+ for (var i = 0; i < $scope.MontageMonitors.length; i++) {
+
+ if ($scope.MontageMonitors[i].Monitor.Id == mid) {
+ m = i;
+ break;
+ }
+ }
+ if (m != -1) {
+ $scope.MontageMonitors[m].Monitor.isPaused = !$scope.MontageMonitors[m].Monitor.isPaused;
+
+ var cmd = $scope.MontageMonitors[m].Monitor.isPaused ? 1 : 2;
+
+ NVRDataModel.debug("Sending CMD:" + cmd + " for monitor " + $scope.MontageMonitors[m].Monitor.Name);
+ controlEventStream(cmd, "", $scope.MontageMonitors[m].Monitor.connKey, -1);
+ }
+ };
+
+
+ $scope.humanizeTime = function (str) {
+ return moment(str).fromNow();
+
+ };
+
+ //--------------------------------------
+ // Called when ion-footer collapses
+ // note that on init it is also called
+ //---------------------------------------
+
+ $scope.footerExpand = function () {
+ // console.log ("**************** EXPAND CALLED ***************");
+ $ionicSideMenuDelegate.canDragContent(false);
+ };
+
+ $scope.footerCollapse = function () {
+
+
+ footerCollapse();
+
+
+ };
+
+
+ /* Note this is also called when the view is first loaded */
+ function footerCollapse() {
+ // console.log ("**************** COLLAPSE CALLED ***************");
+ if (readyToRun == false) {
+ NVRDataModel.debug("fake call to footerCollapse - ignoring");
+ return;
+ }
+
+
+
+ $scope.dragBorder = "";
+ $scope.isDragabillyOn = false;
+ $ionicSideMenuDelegate.canDragContent(true);
+
+ NVRDataModel.stopNetwork("MontageHistory-footerCollapse");
+ var ld = NVRDataModel.getLogin();
+
+ $scope.sliderVal.realRate = $scope.sliderVal.rate * 100;
+ //NVRDataModel.debug ("Playback rate is:" + $scope.sliderVal.realRate);
+
+ var TimeObjectFrom = moment($scope.datetimeValueFrom.value).format("YYYY-MM-DD HH:mm");
+ var TimeObjectTo = moment($scope.datetimeValueTo.value).format('YYYY-MM-DD HH:mm');
+
+ // console.log ("TIME START: " + TimeObjectFrom + " " + TimeObjectTo);
+ //console.log ("TIME START: " + TimeObjectFrom + " " + TimeObjectTo);
+
+
+ var apiurl;
+
+ // release all active streams
+ for (var i = 0; i < $scope.MontageMonitors.length; i++) {
+ $scope.MontageMonitors[i].Monitor.selectStyle = "";
+ // generate new connKeys if timeline changes
+ if ($scope.MontageMonitors[i].Monitor.eventUrl != 'img/noevent.png') {
+ NVRDataModel.log("footerCollapse: Calling kill with " + $scope.MontageMonitors[i].Monitor.connKey + " because url is " + $scope.MontageMonitors[i].Monitor.eventUrl);
+ var tmpCK = angular.copy($scope.MontageMonitors[i].Monitor.connKey);
+ timedControlEventStream(2500, 17, "", tmpCK, -1);
+ $scope.MontageMonitors[i].Monitor.eventUrl = "img/noevent.png";
+ $scope.MontageMonitors[i].Monitor.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ //console.log ("Generating connkey: " +$scope.MontageMonitors[i].Monitor.connKey);
+ } else {
+ //console.log ("footerCollapse: Skipped kill: connkey:"+$scope.MontageMonitors[i].Monitor.connKey + " function " + $scope.MontageMonitors[i].Monitor.Function + " listDisplay " + $scope.MontageMonitors[i].Monitor.lisDisplay + " enabled " + $scope.MontageMonitors[i].Monitor.Enabled + " eventURL " + $scope.MontageMonitors[i].Monitor.eventUrl);
+ }
+
+
+
+
+ }
+ // grab events that start on or before the time and end on or after the time
+ // this should only bring up events that span that time
+ apiurl = ld.apiurl + "/events/index/StartTime >=:" + TimeObjectFrom + "/EndTime <=:" + TimeObjectTo + ".json";
+
+ NVRDataModel.log("Event timeline API is " + apiurl);
+
+ $http.get(apiurl)
+ .success(function (data) {
+
+ var ld = NVRDataModel.getLogin();
+ NVRDataModel.debug("Got " + data.events.length + "new history events...");
+ var eid, mid, stime;
+ for (i = 0; i < data.events.length; i++) {
+ mid = data.events[i].Event.MonitorId;
+ eid = data.events[i].Event.Id;
+ stime = data.events[i].Event.StartTime;
+
+ // only take the first one for each monitor
+ for (var j = 0; j < $scope.MontageMonitors.length; j++) {
+ $scope.MontageMonitors[j].Monitor.isPaused = false;
+ // that's the earliest match and play gapless from there
+ if ($scope.MontageMonitors[j].Monitor.Id == mid) {
+
+ if ($scope.MontageMonitors[j].Monitor.eventUrl == 'img/noevent.png') {
+
+ // console.log ("Old value of event url " + $scope.MontageMonitors[j].eventUrl);
+ //console.log ("ldurl is " + ld.streamingurl);
+
+ var bw = NVRDataModel.getBandwidth() == "lowbw" ? zm.eventMontageQualityLowBW : ld.montageHistoryQuality;
+ $scope.MontageMonitors[j].Monitor.eventUrl = ld.streamingurl + "/nph-zms?source=event&mode=jpeg&event=" + eid + "&frame=1&replay=gapless&rate=" + $scope.sliderVal.realRate + "&connkey=" + $scope.MontageMonitors[j].Monitor.connKey + "&scale=" + bw + "&rand=" + $rootScope.rand;
+ //console.log ("Setting event URL to " +$scope.MontageMonitors[j].Monitor.eventUrl);
+
+ // console.log ("SWITCHING TO " + $scope.MontageMonitors[j].eventUrl);
+
+
+ $scope.MontageMonitors[j].Monitor.eventUrlTime = stime;
+ }
+ }
+ }
+ }
+
+ // make sure we do our best to get that duration for all monitors
+ // in the above call, is possible some did not make the cut in the first page
+
+ NVRDataModel.log("Making sure all monitors have a fair chance...");
+ for (i = 0; i < $scope.MontageMonitors.length; i++) {
+ if ($scope.MontageMonitors[i].Monitor.eventUrl == 'img/noevent.png') {
+
+
+ var indivGrab = ld.apiurl + "/events/index/MonitorId:" + $scope.MontageMonitors[i].Monitor.Id + "/StartTime >=:" + TimeObjectFrom + "/EndTime <=:" + TimeObjectTo + ".json";
+
+ NVRDataModel.debug("Monitor " + $scope.MontageMonitors[i].Monitor.Id + ":" + $scope.MontageMonitors[i].Monitor.Name + " does not have events, trying " + indivGrab);
+
+ getExpandedEvents(i, indivGrab);
+
+ }
+ }
+ console.log("REDOING PACKERY & DRAG");
+ if (pckry !== undefined)
+ $timeout(function () {
+
+
+ draggies.forEach(function (drag) {
+ drag.destroy();
+ });
+
+ draggies = [];
+ pckry.reloadItems();
+ pckry.getItemElements().forEach(function (itemElem) {
+ draggie = new Draggabilly(itemElem);
+ pckry.bindDraggabillyEvents(draggie);
+ draggies.push(draggie);
+ draggie.disable();
+ });
+
+ $timeout(function () {
+ console.log (">>>>> layouting");
+ //pckry.onresize();
+ pckry.layout();
+ }, 100);
+
+ }, 500);
+
+
+ })
+ .error(function (data) {
+ NVRDataModel.debug("history ERROR:" + JSON.stringify(data));
+
+ });
+
+
+ function getExpandedEvents(i, indivGrab) {
+ var ld = NVRDataModel.getLogin();
+ // console.log ("EXPANDED EVENT " + i + " " + indivGrab);
+ $http.get(indivGrab)
+ .success(function (data) {
+ // console.log ("EXPANDED DATA FOR MONITOR " + i + JSON.stringify(data));
+ if (data.events.length > 0) {
+
+ if (!NVRDataModel.isBackground()) {
+
+ var bw = NVRDataModel.getBandwidth() == "lowbw" ? zm.eventMontageQualityLowBW : ld.montageHistoryQuality;
+
+ $scope.MontageMonitors[i].Monitor.eventUrl = ld.streamingurl + "/nph-zms?source=event&mode=jpeg&event=" + data.events[0].Event.Id + "&frame=1&replay=gapless&rate=" + $scope.sliderVal.realRate + "&connkey=" + $scope.MontageMonitors[i].Monitor.connKey + "&scale=" + bw + "&rand=" + $rootScope.rand;
+
+
+
+ //console.log ("SWITCHING TO " + $scope.MontageMonitors[i].eventUrl);
+
+ $scope.MontageMonitors[i].Monitor.eventUrlTime = data.events[0].Event.StartTime;
+
+ NVRDataModel.log("Found expanded event " + data.events[0].Event.Id + " for monitor " + $scope.MontageMonitors[i].Monitor.Id);
+ } else {
+ // $scope.MontageMonitors[i].eventUrl="img/noevent.png";
+ // $scope.MontageMonitors[i].eventUrlTime = "";
+ // NVRDataModel.log ("Setting img src to null as data received in background");
+ }
+ }
+
+ })
+ .error(function (data) {});
+ }
+ }
+
+ //---------------------------------------------------------
+ // This is periodically called to get the current playing
+ // event by zms. I use this to display a timestamp
+ // Its a 2 step process - get event Id then go a Event
+ // API call to get time stamp. Sucks
+ //---------------------------------------------------------
+
+ function checkAllEvents() {
+
+ //console.log ("Events are checked....");
+
+ for (var i = 0; i < $scope.MontageMonitors.length; i++) {
+ // don't check for monitors that are not shown
+ // because nph connkey won't exist and the response
+ // will fail
+ if ($scope.MontageMonitors[i].Monitor.eventUrl != "" && $scope.MontageMonitors[i].Monitor.eventUrl != 'img/noevent.png' && $scope.MontageMonitors[i].Monitor.connKey != '' &&
+ $scope.MontageMonitors[i].Monitor.Function != 'None' &&
+ $scope.MontageMonitors[i].Monitor.listDisplay != 'noshow' &&
+ $scope.MontageMonitors[i].Monitor.Enabled != '0') {
+ NVRDataModel.debug("Checking event status for " + $scope.MontageMonitors[i].Monitor.Name + ":" + $scope.MontageMonitors[i].Monitor.eventUrl + ":" + $scope.MontageMonitors[i].Monitor.Function + ":" + $scope.MontageMonitors[i].Monitor.listDisplay);
+ controlEventStream('99', '', $scope.MontageMonitors[i].Monitor.connKey, i);
+
+ }
+ }
+ }
+
+
+ $scope.dateChanged = function () {
+ // window.stop();
+ // console.log (">>>>>>>>>>>>>>>>>>>>>>>>>>>>> BAD BAD");
+ footerCollapse();
+ };
+
+
+ //--------------------------------------------------------------
+ // Used to control zms for a connkey. If ndx is not -1,
+ // then it also calls an event API for the returned eid
+ // and stores its time in the montage monitors array
+ //--------------------------------------------------------------
+ $scope.controlEventStream = function (cmd, disp, connkey, ndx) {
+ controlEventStream(cmd, disp, connkey, ndx);
+ };
+
+ function timedControlEventStream(mTime, cmd, disp, connkey, ndx) {
+ var mMtime = mTime || 2000;
+ NVRDataModel.debug("Deferring control " + cmd + " by " + mMtime);
+ $timeout(function () {
+ subControlStream(cmd, connkey);
+
+ }, mMtime);
+ }
+
+ function subControlStream(cmd, connkey) {
+ var loginData = NVRDataModel.getLogin();
+ var myauthtoken = $rootScope.authSession.replace("&auth=", "");
+ //&auth=
+ var req = $http({
+ method: 'POST',
+ /*timeout: 15000,*/
+ url: loginData.url + '/index.php',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ //'Accept': '*/*',
+ },
+ transformRequest: function (obj) {
+ var str = [];
+ for (var p in obj)
+ str.push(encodeURIComponent(p) + "=" +
+ encodeURIComponent(obj[p]));
+ var foo = str.join("&");
+ //console.log("****SUB RETURNING " + foo);
+ return foo;
+ },
+
+ data: {
+ view: "request",
+ request: "stream",
+ connkey: connkey,
+ command: cmd,
+ auth: myauthtoken,
+ // user: loginData.username,
+ // pass: loginData.password
+ }
+ });
+
+ req.success(function (resp) {
+ NVRDataModel.debug("subControl success:" + JSON.stringify(resp));
+ });
+
+
+ req.error(function (resp) {
+ NVRDataModel.debug("subControl error:" + JSON.stringify(resp));
+ });
+ }
+
+
+ function controlEventStream(cmd, disp, connkey, ndx) {
+ // console.log("Command value " + cmd);
+
+ if (disp) {
+ $ionicLoading.hide();
+ $ionicLoading.show({
+ template: $translate.instant('kPleaseWait') + "...",
+ noBackdrop: true,
+ duration: zm.loadingTimeout,
+ });
+ }
+ var loginData = NVRDataModel.getLogin();
+
+ /*
+ var CMD_NONE = 0;
+ var CMD_PAUSE = 1;
+ var CMD_PLAY = 2;
+ var CMD_STOP = 3;
+ var CMD_FASTFWD = 4;
+ var CMD_SLOWFWD = 5;
+ var CMD_SLOWREV = 6;
+ var CMD_FASTREV = 7;
+ var CMD_ZOOMIN = 8;
+ var CMD_ZOOMOUT = 9;
+ var CMD_PAN = 10;
+ var CMD_SCALE = 11;
+ var CMD_PREV = 12;
+ var CMD_NEXT = 13;
+ var CMD_SEEK = 14;
+ var CMD_QUERY = 99;
+ */
+
+
+
+ // You need to POST commands to control zms
+ // Note that I am url encoding the parameters into the URL
+ // If I leave it as JSON, it gets converted to OPTONS due
+ // to CORS behaviour and ZM/Apache don't seem to handle it
+
+ //console.log("POST: " + loginData.url + '/index.php');
+
+ //console.log ("AUTH IS " + $rootScope.authSession);
+
+ var myauthtoken = $rootScope.authSession.replace("&auth=", "");
+ //&auth=
+ var req = $http({
+ method: 'POST',
+ /*timeout: 15000,*/
+ url: loginData.url + '/index.php',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ //'Accept': '*/*',
+ },
+ transformRequest: function (obj) {
+ var str = [];
+ for (var p in obj)
+ str.push(encodeURIComponent(p) + "=" +
+ encodeURIComponent(obj[p]));
+ var foo = str.join("&");
+ // console.log("****RETURNING " + foo);
+ return foo;
+ },
+
+ data: {
+ view: "request",
+ request: "stream",
+ connkey: connkey,
+ command: cmd,
+ auth: myauthtoken,
+ // user: loginData.username,
+ // pass: loginData.password
+ }
+ });
+ req.success(function (resp) {
+
+ // console.log("SUCCESS FOR: " + JSON.stringify(resp));
+
+ if (resp.result == "Ok" && ndx != -1) {
+ var ld = NVRDataModel.getLogin();
+ var apiurl = ld.apiurl + "/events/" + resp.status.event + ".json";
+ //console.log ("API " + apiurl);
+ $http.get(apiurl)
+ .success(function (data) {
+ var currentEventTime = moment(data.event.Event.StartTime);
+ var maxTime = moment($scope.datetimeValueTo.value);
+ //NVRDataModel.debug ("Monitor: " + $scope.MontageMonitors[ndx].Monitor.Id + " max time="+maxTime + "("+$scope.datetimeValueTo.value+")"+ " current="+currentEventTime + "("+data.event.Event.StartTime+")");
+ if ($scope.MontageMonitors[ndx].Monitor.eventUrlTime != data.event.Event.StartTime && currentEventTime.diff(maxTime) <= 0) {
+
+ var ld = NVRDataModel.getLogin();
+ var element = angular.element(document.getElementById($scope.MontageMonitors[ndx].Monitor.Id + "-timeline"));
+ element.removeClass('animated flipInX');
+ element.addClass('animated flipOutX');
+ $timeout(function () {
+ element.removeClass('animated flipOutX');
+ element.addClass('animated flipInX');
+ $scope.MontageMonitors[ndx].Monitor.eventUrlTime = data.event.Event.StartTime;
+
+ var bw = NVRDataModel.getBandwidth() == "lowbw" ? zm.eventMontageQualityLowBW : ld.montageHistoryQuality;
+ $scope.MontageMonitors[ndx].Monitor.eventUrl = ld.streamingurl + "/nph-zms?source=event&mode=jpeg&event=" + data.event.Event.Id + "&frame=1&replay=gapless&rate=" + $scope.sliderVal.realRate + "&connkey=" + $scope.MontageMonitors[ndx].Monitor.connKey + "&scale=" + bw + "&rand=" + $rootScope.rand;
+ }, 700);
+
+ } else if (currentEventTime.diff(maxTime) > 0) {
+ NVRDataModel.debug(">>>>>>>>Monitor " + $scope.MontageMonitors[ndx].Monitor.Id + " event time of " + data.event.Event.StartTime + " exceeds " + $scope.datetimeValueTo.value + " stopping...");
+ subControlStream(17, connkey);
+
+
+ }
+
+ })
+ .error(function (data) {
+ $scope.MontageMonitors[ndx].Monitor.eventUrlTime = "-";
+ });
+
+ }
+ });
+
+ req.error(function (resp) {
+ //console.log("ERROR: " + JSON.stringify(resp));
+ NVRDataModel.log("Error sending event command " + JSON.stringify(resp), "error");
+ });
+ }
+
+
+ $scope.closeReorderModal = function () {
+ //console.log("Close & Destroy Monitor Modal");
+ // switch off awake, as liveview is finished
+ //NVRDataModel.setAwake(false);
+ $scope.modal.remove();
+
+ };
+
+
+ $scope.isBackground = function () {
+ return NVRDataModel.isBackground();
+ };
+
+
+ //----------------------------------------------------------------
+ // Alarm notification handling
+ //----------------------------------------------------------------
+ $scope.handleAlarms = function () {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+ if (!$rootScope.isAlarm) {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions({
+ disableBack: true
+ });
+ $state.go("events", {
+ "id": 0
+ }, {
+ reload: true
+ });
+ }
+ };
+
+ $scope.handleAlarmsWhileMinimized = function () {
+ $rootScope.isAlarm = !$rootScope.isAlarm;
+
+ $scope.minimal = !$scope.minimal;
+ NVRDataModel.debug("MontageHistoryCtrl: switch minimal is " + $scope.minimal);
+ ionic.Platform.fullScreen($scope.minimal, !$scope.minimal);
+ $interval.cancel(intervalHandle);
+ $interval.cancel($rootScope.eventQueryInterval);
+
+ if (!$rootScope.isAlarm) {
+ $rootScope.alarmCount = "0";
+ $ionicHistory.nextViewOptions({
+ disableBack: true
+ });
+ $state.go("events", {
+ "id": 0
+ }, {
+ reload: true
+ });
+ }
+ };
+
+
+ //-------------------------------------------------------------
+ // this is checked to make sure we are not pulling images
+ // when app is in background. This is a problem with Android,
+ // for example
+ //-------------------------------------------------------------
+
+ $scope.isBackground = function () {
+ //console.log ("Is background called from Montage and returned " +
+ //NVRDataModel.isBackground());
+ return NVRDataModel.isBackground();
+ };
+
+ //-------------------------------------------------------------
+ // Called when user taps on the reorder button
+ //-------------------------------------------------------------
+
+ $scope.reorderList = function () {
+ //console.log("REORDER");
+ $scope.data.showDelete = false;
+ $scope.data.showReorder = !$scope.data.showReorder;
+ };
+
+ $scope.deleteList = function () {
+ //console.log("DELETE");
+ $scope.data.showDelete = !$scope.data.showDelete;
+ $scope.data.showReorder = false;
+ };
+
+ $scope.reloadReorder = function () {
+ var refresh = NVRDataModel.getMonitors(1);
+
+ refresh.then(function (data) {
+ $scope.monitors = data;
+ $scope.MontageMonitors = data;
+ oldMonitors = angular.copy($scope.monitors);
+ var i;
+ montageOrder = [];
+ for (i = 0; i < $scope.monitors.length; i++) {
+ montageOrder[i] = i;
+ hiddenOrder[i] = 0;
+ }
+
+ loginData.montageOrder = montageOrder.toString();
+ loginData.montageHiddenOrder = hiddenOrder.toString();
+ NVRDataModel.setLogin(loginData);
+ //window.localStorage.setItem("montageOrder", montageOrder.toString());
+ //window.localStorage.setItem("montageHiddenOrder", hiddenOrder.toString());
+ NVRDataModel.log("Montage order saved on refresh: " + montageOrder.toString() + " and hidden order: " + hiddenOrder.toString());
+
+ });
+ };
+
+ $scope.saveReorder = function () {
+ loginData.montageOrder = montageOrder.toString();
+ loginData.montageHiddenOrder = hiddenOrder.toString();
+ NVRDataModel.setLogin(loginData);
+ //window.localStorage.setItem("montageOrder", montageOrder.toString());
+ // window.localStorage.setItem("montageHiddenOrder",
+ // hiddenOrder.toString());
+ //console.log("Saved " + montageOrder.toString());
+ NVRDataModel.log("User press OK. Saved Monitor Order as: " +
+ montageOrder.toString() +
+ " and hidden order as " + hiddenOrder.toString());
+ $scope.modal.remove();
+ };
+
+ $scope.cancelReorder = function () {
+ // user tapped cancel
+ var i, myhiddenorder;
+ if (loginData.montageOrder == '') {
+ //if (window.localStorage.getItem("montageOrder") == undefined) {
+ for (i = 0; i < $scope.MontageMonitors.length; i++) {
+ montageOrder[i] = i;
+ hiddenOrder[i] = 0;
+ }
+ //console.log("Order string is " + montageOrder.toString());
+ NVRDataModel.log("User press Cancel. Reset Monitor Order to: " + montageOrder.toString());
+ } else // montageOrder exists
+ {
+ var myorder = loginData.montageOrder;
+
+ if (loginData.montageHiddenOrder == '') {
+ for (i = 0; i < $scope.MontageMonitors.length; i++) {
+ hiddenOrder[i] = 0;
+ }
+ } else {
+ myhiddenorder = loginData.montageHiddenOrder;
+ hiddenOrder = myhiddenorder.split(",");
+ }
+
+ //console.log("Montage order is " + myorder + " and hidden order is " + myhiddenorder);
+ montageOrder = myorder.split(",");
+
+ for (i = 0; i < montageOrder.length; i++) {
+ montageOrder[i] = parseInt(montageOrder[i]);
+ hiddenOrder[i] = parseInt(hiddenOrder[i]);
+ }
+
+ $scope.MontageMonitors = oldMonitors;
+ NVRDataModel.log("User press Cancel. Restored Monitor Order as: " + montageOrder.toString() + " and hidden order as: " + hiddenOrder.toString());
+
+ }
+ $scope.modal.remove();
+ };
+
+ $scope.toggleReorder = function () {
+ $scope.isReorder = !$scope.isReorder;
+ $scope.data = {};
+ $scope.data.showDelete = false;
+ $scope.data.showReorder = false;
+
+ var i;
+ oldMonitors = angular.copy($scope.monitors);
+ /*for (i=0; i<$scope.monitors.length; i++)
+ {
+ $scope.monitors[i].Monitor.listDisplay="show";
+ }*/
+
+ ld = NVRDataModel.getLogin();
+ if (ld.enableDebug) {
+ // Lets show the re-order list
+ for (i = 0; i < $scope.MontageMonitors.length; i++) {
+ NVRDataModel.debug("Montage reorder list: " + $scope.MontageMonitors[i].Monitor.Name +
+ ":listdisplay->" + $scope.MontageMonitors[i].Monitor.listDisplay);
+
+ }
+ }
+
+ $ionicModal.fromTemplateUrl('templates/reorder-modal.html', {
+ scope: $scope,
+ animation: 'slide-in-up'
+ })
+ .then(function (modal) {
+ $scope.modal = modal;
+ $scope.modal.show();
+ });
+
+
+
+ };
+
+ /*
+ $scope.onSwipeLeft = function ($index) {
+ $scope.showSizeButtons = true;
+ };
+
+ $scope.onSwipeRight = function ($index) {
+ $timeout(function () {
+ $scope.showSizeButtons = false;
+ }, 1000);
+
+ };*/
+
+ //---------------------------------------------------------------------
+ // This marks a monitor as hidden in montage view
+ //---------------------------------------------------------------------
+
+ $scope.deleteItem = function (index) {
+ var findindex = montageOrder.indexOf(index);
+ // $scope.monitors[index].Monitor.Function = 'None';
+ if ($scope.MontageMonitors[index].Monitor.listDisplay == 'show') {
+ $scope.MontageMonitors[index].Monitor.listDisplay = 'noshow';
+ hiddenOrder[findindex] = 1;
+ } else {
+ $scope.MontageMonitors[index].Monitor.listDisplay = 'show';
+ // we need to find the index of Montage Order that contains index
+ // because remember, hiddenOrder does not change its orders as monitors
+ // move
+
+ hiddenOrder[findindex] = 0;
+ }
+ //window.localStorage.setItem("montageOrder", montageOrder.toString());
+ //console.log("DELETE: Order Array now is " + montageOrder.toString());
+ //console.log("DELETE: Hidden Array now is " + hiddenOrder.toString());
+ NVRDataModel.log("Marked monitor " + findindex + " as " + $scope.MontageMonitors[index].Monitor.listDisplay + " in montage");
+
+ };
+
+ //---------------------------------------------------------------------
+ // When we re-arrange the montage, all the ordering index moves
+ // horrible horrible code
+ //---------------------------------------------------------------------
+
+ function reorderItem(item, from, to, reorderHidden) {
+
+ NVRDataModel.debug("MontageHistoryCtrl: Reorder from " + from + " to " + to);
+ $scope.MontageMonitors.splice(from, 1);
+ $scope.MontageMonitors.splice(to, 0, item);
+
+ // Now we need to re-arrange the montageOrder
+ // hiddenOrder remains the same
+
+ var i, j;
+ for (i = 0; i < $scope.monitors.length; i++) {
+ for (j = 0; j < $scope.MontageMonitors.length; j++) {
+ if ($scope.monitors[i].Monitor.Id == $scope.MontageMonitors[j].Monitor.Id) {
+ montageOrder[i] = j;
+ break;
+ }
+ }
+ }
+ NVRDataModel.log("New Montage Order is: " + montageOrder.toString());
+
+ }
+
+
+ $scope.reorderItem = function (item, from, to) {
+ reorderItem(item, from, to, true);
+ };
+
+
+ //---------------------------------------------------------------------
+ // Triggered when you enter/exit full screen
+ //---------------------------------------------------------------------
+ $scope.switchMinimal = function () {
+ $scope.minimal = !$scope.minimal;
+ NVRDataModel.debug("MontageHistoryCtrl: switch minimal is " + $scope.minimal);
+ //console.log("Hide Statusbar");
+ ionic.Platform.fullScreen($scope.minimal, !$scope.minimal);
+ $interval.cancel(intervalHandle); //we will renew on reload
+ // We are reloading this view, so we don't want entry animations
+ $ionicHistory.nextViewOptions({
+ disableAnimate: true,
+ disableBack: true
+ });
+ $state.go("montage", {
+ minimal: $scope.minimal,
+ isRefresh: true
+ });
+ };
+
+
+ $scope.toggleSelectItem = function (ndx) {
+
+ if ($scope.MontageMonitors[ndx].Monitor.selectStyle !== "undefined" && $scope.MontageMonitors[ndx].Monitor.selectStyle == "dragborder-selected") {
+ $scope.MontageMonitors[ndx].Monitor.selectStyle = "";
+ } else {
+ $scope.MontageMonitors[ndx].Monitor.selectStyle = "dragborder-selected";
+ }
+ //console.log ("Switched value to " + $scope.MontageMonitors[ndx].Monitor.selectStyle);
+ };
+ //---------------------------------------------------------------------
+ // Called when you enable/disable dragging
+ //---------------------------------------------------------------------
+
+ $scope.dragToggle = function () {
+ dragToggle();
+
+
+ };
+
+ function dragToggle() {
+ var i;
+ $scope.isDragabillyOn = !$scope.isDragabillyOn;
+
+ $ionicSideMenuDelegate.canDragContent($scope.isDragabillyOn ? false : true);
+
+ //$timeout(function(){pckry.reloadItems();},10);
+ NVRDataModel.debug("setting dragabilly to " + $scope.isDragabillyOn);
+ if ($scope.isDragabillyOn) {
+ $scope.showSizeButtons = true;
+
+ $scope.dragBorder = "dragborder";
+ NVRDataModel.debug("Enabling drag for " + draggies.length + " items");
+ for (i = 0; i < draggies.length; i++) {
+ draggies[i].enable();
+ draggies[i].bindHandles();
+ }
+
+ // reflow and reload as some may be hidden
+ // $timeout(function(){pckry.reloadItems();$timeout(function(){pckry.layout();},300);},100);
+ } else {
+ $scope.dragBorder = "";
+ NVRDataModel.debug("Disabling drag for " + draggies.length + " items");
+ for (i = 0; i < draggies.length; i++) {
+ draggies[i].disable();
+ draggies[i].unbindHandles();
+ }
+ for (i = 0; i < $scope.MontageMonitors.length; i++) {
+ $scope.MontageMonitors[i].Monitor.selectStyle = "";
+ }
+ // reflow and reload as some may be hidden
+ $timeout(function () {
+ $timeout(function () {
+ pckry.shiftLayout();
+ /*var positions = pckry.getShiftPositions('data-item-id');
+ //console.log ("POSITIONS MAP " + JSON.stringify(positions));
+ var ld = NVRDataModel.getLogin();
+ ld.packeryPositions = JSON.stringify(positions);
+ NVRDataModel.setLogin(ld);*/
+ }, 300);
+ }, 100);
+
+ }
+ }
+ //---------------------------------------------------------------------
+ // Show/Hide PTZ control in monitor view
+ //---------------------------------------------------------------------
+ $scope.togglePTZ = function () {
+ $scope.showPTZ = !$scope.showPTZ;
+ };
+
+ $scope.callback = function () {
+ // console.log("dragging");
+ };
+
+
+ $scope.onDropComplete = function (index, obj, event) {
+ //console.log("dragged");
+ var otherObj = $scope.monitors[index];
+ var otherIndex = $scope.monitors.indexOf(obj);
+ $scope.monitors[index] = obj;
+ $scope.monitors[otherIndex] = otherObj;
+ };
+
+
+
+
+ //---------------------------------------------------------------------
+ // changes order of montage display
+ //---------------------------------------------------------------------
+
+ $scope.toggleMontageDisplayOrder = function () {
+ $scope.packMontage = !$scope.packMontage;
+ loginData.packMontage = $scope.packMontage;
+ NVRDataModel.setLogin(loginData);
+ //console.log ("Switching orientation");
+ };
+
+
+ //---------------------------------------------------------------------
+ // In Android, the app runs full steam while in background mode
+ // while in iOS it gets suspended unless you ask for specific resources
+ // So while this view, we DON'T want Android to keep sending 1 second
+ // refreshes to the server for images we are not seeing
+ //---------------------------------------------------------------------
+
+ function onPause() {
+ NVRDataModel.debug("MontageHistoryCtrl: onpause called");
+ $interval.cancel($rootScope.eventQueryInterval);
+ $interval.cancel(intervalHandle);
+
+ // $interval.cancel(modalIntervalHandle);
+
+ // FIXME: Do I need to setAwake(false) here?
+ }
+
+
+ function onResume() {
+
+ // FIXME: Do we need to resume timers? when you resume, you go to portal and then here
+ /*
+ if (!$scope.isModalActive) {
+ var ld = NVRDataModel.getLogin();
+
+
+ NVRDataModel.debug("MontageHistoryCtrl: onresume called");
+ NVRDataModel.log("Restarting eventQuery timer on resume");
+
+ console.log ("************** TIMER STARTED INSIDE RESUME ***************");
+
+ //$rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+ $interval.cancel($rootScope.eventQueryInterval);
+ $rootScope.eventQueryInterval = $interval(function () {
+ checkAllEvents();
+ // console.log ("Refreshing Image...");
+ }.bind(this),zm.eventHistoryTimer);
+ } else // modal is active
+ {
+ // $rootScope.modalRand = Math.floor((Math.random() * 100000) + 1);
+ }*/
+
+
+
+
+ }
+
+ $scope.openMenu = function () {
+ $timeout(function () {
+ $rootScope.stateofSlide = $ionicSideMenuDelegate.isOpen();
+ }, 500);
+
+ $ionicSideMenuDelegate.toggleLeft();
+ };
+
+ $scope.$on('$destroy', function () {
+ NVRDataModel.debug("Cancelling eventQueryInterval");
+ $interval.cancel($rootScope.eventQueryInterval);
+
+
+
+
+ });
+
+
+ $scope.$on('$ionicView.loaded', function () {
+ //console.log("**VIEW ** MontageHistoryCtrl Loaded");
+ });
+
+ $scope.$on('$ionicView.enter', function () {
+ NVRDataModel.debug("**VIEW ** MontageHistory Ctrl Entered");
+ var ld = NVRDataModel.getLogin();
+ //console.log("Setting Awake to " + NVRDataModel.getKeepAwake());
+ NVRDataModel.setAwake(NVRDataModel.getKeepAwake());
+
+ $interval.cancel($rootScope.eventQueryInterval);
+ //console.log ("****************** TIMER STARTED INSIDE ENTER");
+ $rootScope.eventQueryInterval = $interval(function () {
+ checkAllEvents();
+ // console.log ("Refreshing Image...");
+ }.bind(this), zm.eventHistoryTimer);
+
+
+
+
+
+ });
+
+ /*$scope.$on ('$ionicView.unloaded', function() {
+ console.log ("******** HISTORY UNLOADED KILLING WINDOW ************");
+ window.stop();
+ });*/
+
+
+ $scope.$on('$ionicView.beforeEnter', function () {
+
+ // NVRDataModel.log ("Before Enter History: initing connkeys");
+
+
+
+
+
+ });
+
+ $scope.$on('$ionicView.beforeLeave', function () {
+ //console.log("**VIEW ** Event History Ctrl Left, force removing modal");
+ if ($scope.modal) $scope.modal.remove();
+
+
+ NVRDataModel.log("BeforeLeave: Nullifying the streams...");
+
+ for (i = 0; i < $scope.MontageMonitors.length; i++) {
+ var element = document.getElementById("img-" + i);
+ /*if (element)
+ {
+ NVRDataModel.debug("BeforeLeave: Nullifying " + element.src);
+ element.src="";
+ //element.removeAttribute('src');
+
+ //$scope.$apply(nullify(element));
+ //element.src="";
+ }*/
+
+ }
+
+
+
+ NVRDataModel.log("Cancelling event query timer");
+ $interval.cancel($rootScope.eventQueryInterval);
+
+ NVRDataModel.log("MontageHistory:Stopping network pull...");
+ // make sure this is applied in scope digest to stop network pull
+ // thats why we are doing it beforeLeave
+
+ for (i = 0; i < $scope.MontageMonitors.length; i++) {
+ if ($scope.MontageMonitors[i].Monitor.connKey != '' &&
+ $scope.MontageMonitors[i].Monitor.eventUrl != 'img/noevent.png' &&
+ $scope.MontageMonitors[i].Monitor.Function != 'None' &&
+ $scope.MontageMonitors[i].Monitor.lisDisplay != 'noshow' &&
+ $scope.MontageMonitors[i].Monitor.Enabled != '0') {
+ NVRDataModel.log("Before leave: Calling kill with " + $scope.MontageMonitors[i].Monitor.connKey);
+ var tmpCK = angular.copy($scope.MontageMonitors[i].Monitor.connKey);
+ timedControlEventStream(2500, 17, "", tmpCK, -1);
+ }
+ }
+
+ pckry.destroy();
+ window.removeEventListener("resize", orientationChanged, false);
+
+ NVRDataModel.log("Forcing a window.stop() here");
+ NVRDataModel.stopNetwork("MontageHistory-beforeLeave");
+
+
+
+
+ });
+
+ $scope.$on('$ionicView.unloaded', function () {
+
+ });
+
+
+ $scope.sliderChanged = function (dirn) {
+ console.log("SLIDER CHANGED");
+ if ($scope.sliderChanging) {
+ // console.log ("too fast my friend");
+ //$scope.slider.monsize = oldSliderVal;
+ // return;
+ }
+
+ $scope.sliderChanging = true;
+
+ var somethingReset = false;
+
+ // this only changes items that are selected
+ for (var i = 0; i < $scope.MontageMonitors.length; i++) {
+
+ var curVal = parseInt($scope.MontageMonitors[i].Monitor.gridScale);
+ curVal = curVal + (10 * dirn);
+ if (curVal < 10) curVal = 10;
+ if (curVal > 100) curVal = 100;
+ //console.log ("For Index: " + i + " From: " + $scope.MontageMonitors[i].Monitor.gridScale + " To: " + curVal);
+
+ if ($scope.isDragabillyOn) {
+ // only do this for selected monitors
+ if ($scope.MontageMonitors[i].Monitor.selectStyle == "dragborder-selected") {
+
+ $scope.MontageMonitors[i].Monitor.gridScale = curVal;
+ somethingReset = true;
+ }
+ } else {
+ $scope.MontageMonitors[i].Monitor.gridScale = curVal;
+ //somethingReset = true;
+
+ }
+
+ }
+
+ // this changes all items if none were selected
+ if (!somethingReset && $scope.isDragabillyOn) // nothing was selected
+ {
+ for (i = 0; i < $scope.MontageMonitors.length; i++) {
+ var cv = parseInt($scope.MontageMonitors[i].Monitor.gridScale);
+ cv = cv + (10 * dirn);
+ if (cv < 10) cv = 10;
+ if (cv > 100) cv = 100;
+ $scope.MontageMonitors[i].Monitor.gridScale = cv;
+ }
+ }
+
+
+ //pckry.reloadItems();
+
+ pckry.once('layoutComplete', function () {
+ /* $timeout(function () {
+ var positions = pckry.EHgetShiftPositions('eh-data-item-id');
+ //console.log ("POSITIONS MAP " + JSON.stringify(positions));
+ var ld = NVRDataModel.getLogin();
+ ld.EHpackeryPositions = JSON.stringify(positions);
+ NVRDataModel.setLogin(ld);
+ $ionicLoading.hide();
+ $scope.sliderChanging = false;
+ }, zm.packeryTimer);*/
+ });
+
+
+
+ if (!somethingReset) {
+ //console.log (">>>SOMETHING NOT RESET");
+ $timeout(function () {
+ pckry.layout();
+ }, zm.packeryTimer);
+ } else {
+
+ //console.log (">>>SOMETHING RESET");
+ $timeout(function () {
+ layout(pckry);
+ }, zm.packeryTimer);
+ }
+
+
+
+
+ };
+
+ function layout(pckry) {
+ pckry.shiftLayout();
+ }
+
+
+
+ $scope.resetSizes = function () {
+ var somethingReset = false;
+ for (var i = 0; i < $scope.MontageMonitors.length; i++) {
+ if ($scope.isDragabillyOn) {
+ if ($scope.MontageMonitors[i].Monitor.selectStyle == "dragborder-selected") {
+ $scope.MontageMonitors[i].Monitor.gridScale = "50";
+ somethingReset = true;
+ }
+ } else {
+ $scope.MontageMonitors[i].Monitor.gridScale = "50";
+ // somethingReset = true;
+ }
+ }
+ if (!somethingReset && $scope.isDragabillyOn) // nothing was selected
+ {
+ for (i = 0; i < $scope.MontageMonitors.length; i++) {
+ $scope.MontageMonitors[i].Monitor.gridScale = "50";
+ }
+ }
+
+ $timeout(function () {
+ pckry.reloadItems();
+
+
+ $timeout(function () {
+ pckry.layout();
+ }, zm.packeryTimer); // force here - no shiftlayout
+
+
+ }, 100);
+
+ };
+
+
+ //---------------------------------------------------------
+ // This function readjusts montage size
+ // and stores current size to persistent memory
+ //---------------------------------------------------------
+
+ function processSliderChanged(val) {
+ if (sizeInProgress) return;
+
+ sizeInProgress = true;
+ //console.log('Size has changed');
+ NVRDataModel.setMontageSize(val);
+ //console.log("ZMData Montage is " + NVRDataModel.getMontageSize() +
+ // " and slider montage is " + $scope.slider.monsize);
+ // Now go ahead and reset sizes of entire monitor array
+ var monsizestring = "";
+ var i;
+ for (i = 0; i < $scope.monitors.length; i++) {
+
+ $scope.monitorSize[i] = parseInt(NVRDataModel.getMontageSize());
+ //console.log("Resetting Monitor " + i + " size to " + $scope.monitorSize[i]);
+ $scope.scaleDirection[i] = 1;
+ monsizestring = monsizestring + $scope.monitorSize[i] + ':';
+ }
+ monsizestring = monsizestring.slice(0, -1); // kill last :
+ //console.log("Setting monsize string:" + monsizestring);
+ loginData.montageArraySize = monsizestring;
+ NVRDataModel.setLogin(loginData);
+ //window.localStorage.setItem("montageArraySize", monsizestring);
+ sizeInProgress = false;
+ }
+
+ function isEmpty(obj) {
+ for (var prop in obj) {
+ return false;
+ }
+ return true;
+ }
+
+ // called by afterEnter to load Packery
+ function initPackery() {
+
+ console.log(">>>>>>>>>>>>>>>>> HIT INIT");
+ $ionicLoading.show({
+ template: $translate.instant('kArrangingImages'),
+ noBackdrop: true,
+ duration: zm.loadingTimeout
+ });
+
+ var progressCalled = false;
+ draggies = [];
+ var layouttype = true;
+ var ld = NVRDataModel.getLogin();
+
+
+ var positionsStr = ld.packeryPositions;
+ var positions = {};
+
+ // if (positionsStr == '') {
+ if (1) {
+
+ NVRDataModel.log("Did NOT find a EHpackery layout");
+ layouttype = true;
+ } else {
+
+ //console.log ("POSITION STR IS " + positionsStr);
+ positions = JSON.parse(positionsStr);
+ NVRDataModel.log("found a EHpackery layout");
+ console.log(positionsStr);
+ layouttype = false;
+ }
+
+
+ var cnt = 0;
+ $scope.MontageMonitors.forEach(function (elem) {
+
+ if ((elem.Monitor.Enabled != '0') && (elem.Monitor.Function != 'None'))
+ cnt++;
+ });
+
+ NVRDataModel.log("Monitors that are active and not DOM hidden: " + cnt + " while grid has " + positions.length);
+
+ if (cnt > NVRDataModel.getLogin().maxMontage) {
+ cnt = NVRDataModel.getLogin().maxMontage;
+ NVRDataModel.log("restricting monitor count to " + cnt + " due to max-montage setting");
+ }
+
+ /*if (cnt != positions.length) {
+
+ NVRDataModel.log("Whoops!! Monitors have changed. I'm resetting layouts, sorry!");
+ layouttype = true;
+ positions = {};
+ }*/
+
+ var elem = angular.element(document.getElementById("mygrid"));
+
+ //console.log ("**** mygrid is " + JSON.stringify(elem));
+
+ imagesLoaded(elem).on('progress', function (instance, img) {
+
+ progressCalled = true;
+
+ // if (layouttype) $timeout (function(){layout(pckry);},100);
+ });
+
+ imagesLoaded(elem).once('always', function () {
+ console.log("******** ALL IMAGES LOADED");
+ NVRDataModel.debug("All images loaded");
+ $scope.allImagesLoaded = true;
+
+ $ionicLoading.hide();
+
+ pckry = new Packery('.grid', {
+ itemSelector: '.grid-item',
+ percentPosition: true,
+ columnWidth: '.grid-sizer',
+ gutter: 0,
+ initLayout: layouttype
+
+ });
+ if (!progressCalled) {
+ NVRDataModel.log("*** BUG PROGRESS WAS NOT CALLED");
+ pckry.reloadItems();
+
+ }
+
+ $timeout(function () {
+ var cnt = 0;
+ pckry.getItemElements().forEach(function (itemElem) {
+
+ console.log("DRAG ADD " + cnt++);
+ draggie = new Draggabilly(itemElem);
+ pckry.bindDraggabillyEvents(draggie);
+ draggies.push(draggie);
+ draggie.disable();
+ draggie.unbindHandles();
+ });
+
+ pckry.on('dragItemPositioned', itemDragged);
+
+
+
+ if (!isEmpty(positions)) {
+ NVRDataModel.log("Arranging as per packery grid");
+
+ for (var i = 0; i < $scope.MontageMonitors.length; i++) {
+ for (var j = 0; j < positions.length; j++) {
+ if ($scope.MontageMonitors[i].Monitor.Id == positions[j].attr) {
+ $scope.MontageMonitors[i].Monitor.gridScale = positions[j].size;
+ $scope.MontageMonitors[i].Monitor.listDisplay = positions[j].display;
+ NVRDataModel.debug("Setting monitor ID: " + $scope.MontageMonitors[i].Monitor.Id + " to size: " + positions[j].size + " and display:" + positions[j].display);
+ }
+ //console.log ("Index:"+positions[j].attr+ " with size: " + positions[j].size);
+ }
+ }
+
+
+ NVRDataModel.debug("All images loaded, doing image layout");
+ /* $timeout(function () {
+ pckry.initShiftLayout(positions, 'eh-data-item-id');
+ }, 0);*/
+ }
+ $timeout(function () {
+ NVRDataModel.log("Force calling resize");
+ pckry.layout();
+ }, zm.packeryTimer); // don't ask
+
+
+
+ }, zm.packeryTimer);
+
+ });
+
+ function itemDragged(item) {
+ NVRDataModel.debug("drag complete");
+
+ //pckry.getItemElements().forEach(function (itemElem) {
+
+ //console.log (itemElem.attributes['data-item-id'].value+" size "+itemElem.attributes['data-item-size'].value );
+ // });
+
+ /* var positions = pckry.EHgetShiftPositions('eh-data-item-id');
+ //console.log ("POSITIONS MAP " + JSON.stringify(positions));
+ var ld = NVRDataModel.getLogin();
+ ld.EHpackeryPositions = JSON.stringify(positions);
+ NVRDataModel.setLogin(ld);*/
+ }
+
+
+ }
+
+
+ $scope.$on('$ionicView.afterEnter', function () {
+ // This rand is really used to reload the monitor image in img-src so it is not cached
+ // I am making sure the image in montage view is always fresh
+ // I don't think I am using this anymore FIXME: check and delete if needed
+ // $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+
+ $timeout(function () {
+ initPackery();
+ }, zm.packeryTimer);
+ });
+
+ $scope.reloadView = function () {
+ $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+ NVRDataModel.log("User action: image reload " + $rootScope.rand);
+ };
+
+ $scope.doRefresh = function () {
+
+
+
+ //console.log("***Pull to Refresh, recomputing Rand");
+ NVRDataModel.log("Reloading view for montage view, recomputing rand");
+ $rootScope.rand = Math.floor((Math.random() * 100000) + 1);
+ $scope.monitors = [];
+ imageLoadingDataShare.set(0);
+
+ var refresh = NVRDataModel.getMonitors(1);
+
+ refresh.then(function (data) {
+ $scope.monitors = data;
+ $scope.$broadcast('scroll.refreshComplete');
+ });
+ };
+
+ //---------------------------------------------------------------------
+ // Controller main
+ //---------------------------------------------------------------------
+
+
+
+ var intervalHandle;
+ $scope.isModalActive = false;
+ var modalIntervalHandle;
+
+ window.addEventListener("resize", orientationChanged, false);
+
+ document.addEventListener("pause", onPause, false);
+ document.addEventListener("resume", onResume, false);
+
+ $scope.timeFormat = "yyyy-MM-dd " + NVRDataModel.getTimeFormat();
+ $scope.displayDateTimeSliders = true;
+ $scope.showtimers = true;
+ $scope.loginData = NVRDataModel.getLogin();
+
+ var curYear = new Date().getFullYear();
+
+ var readyToRun = false;
+ var i;
+
+ $scope.sliderVal = {
+
+ rate: 1,
+ realRate: 100,
+ hideNoEvents: false,
+ enableGapless: true,
+ exactMatch: false,
+ showTimeline: true
+
+ };
+
+
+
+ // default = start of day
+ var timeto = moment();
+ var timefrom = moment().startOf('day');
+
+ $scope.sliderVal.rate = 1;
+ $scope.sliderVal.realRate = $scope.sliderVal.rate * 100;
+
+
+
+ //var tdatetimeValueFrom = new Date();
+ //tdatetimeValueFrom.setDate(tdatetimeValueFrom.getDate()-1);
+
+ $scope.datetimeValueFrom = {
+ value: ""
+ };
+ $scope.datetimeValueTo = {
+ value: ""
+ };
+
+ $scope.datetimeValueFrom.value = timefrom.toDate();
+ $scope.datetimeValueTo.value = timeto.toDate();
+
+ $rootScope.eventQueryInterval = "";
+
+
+ var commonCss = {
+
+ background: {
+ "background-color": "silver"
+ },
+ before: {
+ "background-color": "purple"
+ },
+ default: {
+ "background-color": "white"
+ }, // default value: 1px
+ after: {
+ "background-color": "green"
+ }, // zone after default value
+ pointer: {
+ "background-color": "red"
+ }, // circle pointer
+ range: {
+ "background-color": "red"
+ } // use it if double value
+ };
+
+
+ $scope.slider_modal_options_rate = {
+ from: 1,
+ to: 10,
+ realtime: true,
+ step: 1,
+ className: "mySliderClass",
+ //modelLabels:function(val) {return "";},
+ smooth: false,
+ css: commonCss,
+ dimension: 'X'
+
+ };
+
+
+
+ var isLongPressActive = false;
+ $scope.isReorder = false;
+ var intervalHandleMontage; // will hold image resize timer on long press
+ var montageIndex = 0; // will hold monitor ID to scale in timer
+
+ var gridcontainer, pckry, draggie, draggies;
+
+ $scope.monitorSize = []; // array with montage sizes per monitor
+ $scope.scaleDirection = []; // 1 = increase -1 = decrease
+
+ $scope.slider = {};
+
+ //console.log ("************ HISTORY " + NVRDataModel.getMontageSize());
+ $scope.slider.monsize = NVRDataModel.getMontageSize();
+ $scope.revMonSize = 11 - parseInt($scope.slider.monsize);
+
+ // The difference between old and original is this:
+ // old will have a copy of the last re-arranged monitor list
+ // while original will have a copy of the order returned by ZM
+
+ var oldMonitors = []; // To keep old order if user cancels after sort;
+
+ // Montage display order may be different so don't
+ // mangle monitors as it will affect other screens
+ // in Montage screen we will work with this local copy
+ //$scope.MontageMonitors = angular.copy ($scope.monitors);
+
+ var montageOrder = []; // This array will keep the ordering in montage view
+ var hiddenOrder = []; // 1 = hide, 0 = don't hide
+
+ var tempMonitors = message;
+ if (tempMonitors.length == 0) {
+ $rootScope.zmPopup = $ionicPopup.alert({
+ title: $translate.instant('kNoMonitors'),
+ template: $translate.instant('kPleaseCheckCredentials')
+ });
+ $ionicHistory.nextViewOptions({
+ disableBack: true
+ });
+ $state.go("login");
+ return;
+ }
+
+ // console.log ("TEMP MONITORS IS " + JSON.stringify(tempMonitors));
+ var tempResponse = NVRDataModel.applyMontageMonitorPrefs(message, 0);
+ $scope.monitors = tempResponse[0];
+ montageOrder = tempResponse[1];
+ hiddenOrder = tempResponse[2];
+
+ NVRDataModel.log("Inside MontageHistoryCtrl:We found " + $scope.monitors.length + " monitors");
+
+ $scope.MontageMonitors = NVRDataModel.applyMontageMonitorPrefs(message, 1)[0];
+
+ var loginData = NVRDataModel.getLogin();
+
+ $scope.packMontage = loginData.packMontage;
+
+
+ // init monitors
+ NVRDataModel.debug(">>Initializing connkeys and images...");
+ for (i = 0; i < $scope.MontageMonitors.length; i++) {
+ //$scope.MontageMonitors[i].Monitor.connKey='';
+
+
+
+ $scope.MontageMonitors[i].Monitor.connKey = (Math.floor((Math.random() * 999999) + 1)).toString();
+ $scope.MontageMonitors[i].Monitor.eventUrl = 'img/noevent.png';
+ $scope.MontageMonitors[i].Monitor.eventUrlTime = "";
+ $scope.MontageMonitors[i].Monitor.isPaused = false;
+ $scope.MontageMonitors[i].Monitor.gridScale = "50";
+ $scope.MontageMonitors[i].Monitor.selectStyle = "";
+ $scope.MontageMonitors[i].Monitor.alarmState = 'color:rgba(0,0,0,0);';
+
+ }
+ readyToRun = true;
+
+
+
+ // --------------------------------------------------------
+ // Handling of back button in case modal is open should
+ // close the modal
+ // --------------------------------------------------------
+
+ $ionicPlatform.registerBackButtonAction(function (e) {
+ e.preventDefault();
+ if ($scope.modal && $scope.modal.isShown()) {
+ // switch off awake, as liveview is finished
+ NVRDataModel.debug("Modal is open, closing it");
+ NVRDataModel.setAwake(false);
+ $scope.modal.remove();
+ $scope.isModalActive = false;
+ } else {
+ NVRDataModel.debug("Modal is closed, so toggling or exiting");
+ if (!$ionicSideMenuDelegate.isOpenLeft()) {
+ $ionicSideMenuDelegate.toggleLeft();
+
+ } else {
+ navigator.app.exitApp();
+ }
+
+ }
+
+ }, 1000);
+
+
+
+
+ $scope.showSizeButtons = false;
+ $ionicPopover.fromTemplateUrl('templates/help/montage-help.html', {
+ scope: $scope,
+ }).then(function (popover) {
+ $scope.popover = popover;
+ });
+
+ var timestamp = new Date().getUTCMilliseconds();
+ $scope.minimal = $stateParams.minimal;
+ $scope.zmMarginTop = $scope.minimal ? 0 : 15;
+ //console.log ("********* MARGIN IS " + $scope.zmMarginTop);
+
+ $scope.isRefresh = $stateParams.isRefresh;
+ var sizeInProgress = false;
+ $scope.imageStyle = true;
+
+ $ionicSideMenuDelegate.canDragContent(true);
+
+
+
+ // Do we have a saved montage array size? No?
+ // if (window.localStorage.getItem("montageArraySize") == undefined) {
+ if (loginData.montageArraySize == '0') {
+
+ for (i = 0; i < $scope.monitors.length; i++) {
+ $scope.monitorSize.push(NVRDataModel.getMontageSize());
+ $scope.scaleDirection.push(1);
+ }
+ } else // recover previous settings
+ {
+ var msize = loginData.montageArraySize;
+ //console.log("MontageArrayString is=>" + msize);
+ $scope.monitorSize = msize.split(":");
+ var j;
+
+ for (j = 0; j < $scope.monitorSize.length; j++) {
+ // convert to number other wise adding to it concatenates :-)
+ $scope.monitorSize[j] = parseInt($scope.monitorSize[j]);
+ $scope.scaleDirection.push(1);
+ //console.log("Montage size for monitor " + j + " is " + $scope.monitorSize[j]);
+
+ }
+
+ }
+ // $scope.monitorSize = monitorSize;
+ // $scope.scaleDirection = scaleDirection;
+
+ $scope.LoginData = NVRDataModel.getLogin();
+ $scope.monLimit = $scope.LoginData.maxMontage;
+
+ if ($rootScope.platformOS != 'ios') {
+ NVRDataModel.log("Limiting montage to 5, thanks to Chrome's stupid connection limit");
+ $scope.monLimit = 5;
+ }
+
+ //console.log("********* Inside MontageHistoryCtrl, MAX LIMIT=" + $scope.monLimit);
+
+
+ $rootScope.authSession = "undefined";
+ $ionicLoading.show({
+ template: $translate.instant('kNegotiatingStreamAuth'),
+ animation: 'fade-in',
+ showBackdrop: true,
+ duration: zm.loadingTimeout,
+ maxWidth: 300,
+ showDelay: 0
+ });
+
+
+ var ld = NVRDataModel.getLogin();
+
+ //console.log ("MONITORS " + JSON.stringify($scope.monitors));
+ $rootScope.validMonitorId = $scope.monitors[0].Monitor.Id;
+ NVRDataModel.getAuthKey($rootScope.validMonitorId)
+ .then(function (success) {
+ $ionicLoading.hide();
+ //console.log(success);
+ $rootScope.authSession = success;
+ NVRDataModel.log("Stream authentication construction: " +
+ $rootScope.authSession);
+
+ },
+ function (error) {
+
+ $ionicLoading.hide();
+ NVRDataModel.debug("MontageHistoryCtrl: Error in authkey retrieval " + error);
+ //$rootScope.authSession="";
+ NVRDataModel.log("MontageHistoryCtrl: Error returned Stream authentication construction. Retaining old value of: " + $rootScope.authSession);
+ });
+
+
+
+
+
+
+
+}]); \ No newline at end of file
diff --git a/www/js/app.js b/www/js/app.js
index 034a89c6..b543efaf 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -28,7 +28,8 @@ angular.module('zmApp', [
'com.2fdevs.videogular.plugins.overlayplay',
'ionic-native-transitions',
'mgo-angular-wizard',
- 'pascalprecht.translate'
+ 'pascalprecht.translate',
+ 'jett.ionic.scroll.sista'
@@ -1265,7 +1266,10 @@ angular.module('zmApp', [
}
if (window.StatusBar) {
// org.apache.cordova.statusbar required
+ NVRDataModel.log("Updating statusbar");
StatusBar.styleDefault();
+ //StatusBar.overlaysWebView(false);
+ StatusBar.backgroundColorByHexString("#2980b9");
}
@@ -1762,7 +1766,7 @@ angular.module('zmApp', [
},
url: "/montage-history",
- templateUrl: "templates/montage-history.html",
+ templateUrl: "templates/montage-history2.html",
controller: 'zmApp.MontageHistoryCtrl',
params: {
minimal: false,
diff --git a/www/lib/ionic-content-banner/.bower.json b/www/lib/ionic-content-banner/.bower.json
new file mode 100644
index 00000000..b5f74569
--- /dev/null
+++ b/www/lib/ionic-content-banner/.bower.json
@@ -0,0 +1,10 @@
+{
+ "name": "ionic-content-banner",
+ "_cacheHeaders": {
+ "Content-Type": "text/html; charset=utf-8"
+ },
+ "main": "index",
+ "_source": "https://github.com/pliablepixels/ionic-content-banner",
+ "_target": "*",
+ "_originalSource": "https://github.com/pliablepixels/ionic-content-banner"
+} \ No newline at end of file
diff --git a/www/lib/ionic-content-banner/index b/www/lib/ionic-content-banner/index
new file mode 100644
index 00000000..7cd1a9cd
--- /dev/null
+++ b/www/lib/ionic-content-banner/index
@@ -0,0 +1,994 @@
+
+
+
+
+<!DOCTYPE html>
+<html lang="en" class="">
+ <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# object: http://ogp.me/ns/object# article: http://ogp.me/ns/article# profile: http://ogp.me/ns/profile#">
+ <meta charset='utf-8'>
+
+
+ <link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/frameworks-4736542a9762352d9c02b0e277f6ccd6d3d8d4e5bf6bf9eba242c089a1d313cc.css" media="all" rel="stylesheet" />
+ <link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/github-32986c0e3d905f34959de0ab6880e1cb667dfeaa0d61a2b6c6d1c947c9c2cad7.css" media="all" rel="stylesheet" />
+
+
+ <link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/site-f6ce114ac3bc145f575863b4a6dbdf65e924bccb184fc4d4a4f5a09819b4173d.css" media="all" rel="stylesheet" />
+
+
+ <link as="script" href="https://assets-cdn.github.com/assets/frameworks-411d610e4b2b1ffa3d063904534406d08c8351d9ba9b4b3f339bab35e86bb4f3.js" rel="preload" />
+
+ <link as="script" href="https://assets-cdn.github.com/assets/github-877cf6bfb710d9ccb14e4a6841ffe0fe6b52fec0f230b1f594ed779dc6ba0f94.js" rel="preload" />
+
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta http-equiv="Content-Language" content="en">
+ <meta name="viewport" content="width=device-width">
+
+ <title>GitHub - pliablepixels/ionic-content-banner: An informational content banner for Ionic applications</title>
+ <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">
+ <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
+ <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png">
+ <link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png">
+ <link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png">
+ <link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png">
+ <link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png">
+ <link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png">
+ <link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png">
+ <link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png">
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png">
+ <meta property="fb:app_id" content="1401488693436528">
+
+ <meta content="https://avatars0.githubusercontent.com/u/4116654?v=3&amp;s=400" name="twitter:image:src" /><meta content="@github" name="twitter:site" /><meta content="summary" name="twitter:card" /><meta content="pliablepixels/ionic-content-banner" name="twitter:title" /><meta content="ionic-content-banner - An informational content banner for Ionic applications" name="twitter:description" />
+ <meta content="https://avatars0.githubusercontent.com/u/4116654?v=3&amp;s=400" property="og:image" /><meta content="GitHub" property="og:site_name" /><meta content="object" property="og:type" /><meta content="pliablepixels/ionic-content-banner" property="og:title" /><meta content="https://github.com/pliablepixels/ionic-content-banner" property="og:url" /><meta content="ionic-content-banner - An informational content banner for Ionic applications" property="og:description" />
+ <meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">
+ <meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">
+ <link rel="assets" href="https://assets-cdn.github.com/">
+
+ <meta name="pjax-timeout" content="1000">
+
+ <meta name="request-id" content="32BE7664:1A6B0:92D092A:57EC009B" data-pjax-transient>
+
+ <meta name="msapplication-TileImage" content="/windows-tile.png">
+ <meta name="msapplication-TileColor" content="#ffffff">
+ <meta name="selected-link" value="repo_source" data-pjax-transient>
+
+ <meta name="google-site-verification" content="KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU">
+<meta name="google-site-verification" content="ZzhVyEFwb7w3e0-uOTltm8Jsck2F5StVihD0exw2fsA">
+ <meta name="google-analytics" content="UA-3769691-2">
+
+<meta content="collector.githubapp.com" name="octolytics-host" /><meta content="github" name="octolytics-app-id" /><meta content="32BE7664:1A6B0:92D092A:57EC009B" name="octolytics-dimension-request_id" />
+<meta content="/&lt;user-name&gt;/&lt;repo-name&gt;" data-pjax-transient="true" name="analytics-location" />
+
+
+
+ <meta class="js-ga-set" name="dimension1" content="Logged Out">
+
+
+
+ <meta name="hostname" content="github.com">
+ <meta name="user-login" content="">
+
+ <meta name="expected-hostname" content="github.com">
+ <meta name="js-proxy-site-detection-payload" content="YWU2OThlMzk2YjI4NjJkY2ZmNWVlNDRmMjE3NTA5ZTY1ZWFkNGU0NTA0Mzg2ZmQzNzRlYTI3YTg1OWVlMDFiZHx7InJlbW90ZV9hZGRyZXNzIjoiNTAuMTkwLjExOC4xMDAiLCJyZXF1ZXN0X2lkIjoiMzJCRTc2NjQ6MUE2QjA6OTJEMDkyQTo1N0VDMDA5QiIsInRpbWVzdGFtcCI6MTQ3NTA4NDQ0M30=">
+
+
+ <link rel="mask-icon" href="https://assets-cdn.github.com/pinned-octocat.svg" color="#4078c0">
+ <link rel="icon" type="image/x-icon" href="https://assets-cdn.github.com/favicon.ico">
+
+ <meta name="html-safe-nonce" content="9cfac0674d88b04428da35525c95a48f9ba043e2">
+ <meta content="c8b66cb37a2451ec561568d352d4ce24d539cf6a" name="form-nonce" />
+
+ <meta http-equiv="x-pjax-version" content="129b01be0c3118dfa772025e0c9dcf56">
+
+
+
+ <meta name="description" content="ionic-content-banner - An informational content banner for Ionic applications">
+ <meta name="go-import" content="github.com/pliablepixels/ionic-content-banner git https://github.com/pliablepixels/ionic-content-banner.git">
+
+ <meta content="4116654" name="octolytics-dimension-user_id" /><meta content="pliablepixels" name="octolytics-dimension-user_login" /><meta content="67287661" name="octolytics-dimension-repository_id" /><meta content="pliablepixels/ionic-content-banner" name="octolytics-dimension-repository_nwo" /><meta content="true" name="octolytics-dimension-repository_public" /><meta content="true" name="octolytics-dimension-repository_is_fork" /><meta content="39717988" name="octolytics-dimension-repository_parent_id" /><meta content="djett41/ionic-content-banner" name="octolytics-dimension-repository_parent_nwo" /><meta content="39717988" name="octolytics-dimension-repository_network_root_id" /><meta content="djett41/ionic-content-banner" name="octolytics-dimension-repository_network_root_nwo" />
+ <link href="https://github.com/pliablepixels/ionic-content-banner/commits/master.atom" rel="alternate" title="Recent Commits to ionic-content-banner:master" type="application/atom+xml">
+
+
+ <link rel="canonical" href="https://github.com/pliablepixels/ionic-content-banner" data-pjax-transient>
+ </head>
+
+
+ <body class="logged-out env-production vis-public fork">
+ <div id="js-pjax-loader-bar" class="pjax-loader-bar"><div class="progress"></div></div>
+ <a href="#start-of-content" tabindex="1" class="accessibility-aid js-skip-to-content">Skip to content</a>
+
+
+
+
+
+
+
+ <header class="site-header js-details-container" role="banner">
+ <div class="container-responsive">
+ <a class="header-logo-invertocat" href="https://github.com/" aria-label="Homepage" data-ga-click="(Logged out) Header, go to homepage, icon:logo-wordmark">
+ <svg aria-hidden="true" class="octicon octicon-mark-github" height="32" version="1.1" viewBox="0 0 16 16" width="32"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path></svg>
+ </a>
+
+ <button class="btn-link float-right site-header-toggle js-details-target" type="button" aria-label="Toggle navigation">
+ <svg aria-hidden="true" class="octicon octicon-three-bars" height="24" version="1.1" viewBox="0 0 12 16" width="18"><path d="M11.41 9H.59C0 9 0 8.59 0 8c0-.59 0-1 .59-1H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1h.01zm0-4H.59C0 5 0 4.59 0 4c0-.59 0-1 .59-1H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1h.01zM.59 11H11.4c.59 0 .59.41.59 1 0 .59 0 1-.59 1H.59C0 13 0 12.59 0 12c0-.59 0-1 .59-1z"></path></svg>
+ </button>
+
+ <div class="site-header-menu">
+ <nav class="site-header-nav site-header-nav-main">
+ <a href="/personal" class="js-selected-navigation-item nav-item nav-item-personal" data-ga-click="Header, click, Nav menu - item:personal" data-selected-links="/personal /personal">
+ Personal
+</a> <a href="/open-source" class="js-selected-navigation-item nav-item nav-item-opensource" data-ga-click="Header, click, Nav menu - item:opensource" data-selected-links="/open-source /open-source">
+ Open source
+</a> <a href="/business" class="js-selected-navigation-item nav-item nav-item-business" data-ga-click="Header, click, Nav menu - item:business" data-selected-links="/business /business/partners /business/features /business/customers /business">
+ Business
+</a> <a href="/explore" class="js-selected-navigation-item nav-item nav-item-explore" data-ga-click="Header, click, Nav menu - item:explore" data-selected-links="/explore /trending /trending/developers /integrations /integrations/feature/code /integrations/feature/collaborate /integrations/feature/ship /explore">
+ Explore
+</a> </nav>
+
+ <div class="site-header-actions">
+ <a class="btn btn-primary site-header-actions-btn" href="/join?source=header-repo" data-ga-click="(Logged out) Header, clicked Sign up, text:sign-up">Sign up</a>
+ <a class="btn site-header-actions-btn mr-2" href="/login?return_to=%2Fpliablepixels%2Fionic-content-banner" data-ga-click="(Logged out) Header, clicked Sign in, text:sign-in">Sign in</a>
+ </div>
+
+ <nav class="site-header-nav site-header-nav-secondary">
+ <a class="nav-item" href="/pricing">Pricing</a>
+ <a class="nav-item" href="/blog">Blog</a>
+ <a class="nav-item" href="https://help.github.com">Support</a>
+ <a class="nav-item header-search-link" href="https://github.com/search">Search GitHub</a>
+ <div class="header-search scoped-search site-scoped-search js-site-search" role="search">
+ <!-- </textarea> --><!-- '"` --><form accept-charset="UTF-8" action="/pliablepixels/ionic-content-banner/search" class="js-site-search-form" data-scoped-search-url="/pliablepixels/ionic-content-banner/search" data-unscoped-search-url="/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
+ <label class="form-control header-search-wrapper js-chromeless-input-container">
+ <div class="header-search-scope">This repository</div>
+ <input type="text"
+ class="form-control header-search-input js-site-search-focus js-site-search-field is-clearable"
+ data-hotkey="s"
+ name="q"
+ placeholder="Search"
+ aria-label="Search this repository"
+ data-unscoped-placeholder="Search GitHub"
+ data-scoped-placeholder="Search"
+ autocapitalize="off">
+ </label>
+</form></div>
+
+ </nav>
+ </div>
+ </div>
+</header>
+
+
+
+ <div id="start-of-content" class="accessibility-aid"></div>
+
+ <div id="js-flash-container">
+</div>
+
+
+ <div role="main">
+ <div itemscope itemtype="http://schema.org/SoftwareSourceCode">
+ <div id="js-repo-pjax-container" data-pjax-container>
+
+<div class="pagehead repohead instapaper_ignore readability-menu experiment-repo-nav">
+ <div class="container repohead-details-container">
+
+
+
+<ul class="pagehead-actions">
+
+ <li>
+ <a href="/login?return_to=%2Fpliablepixels%2Fionic-content-banner"
+ class="btn btn-sm btn-with-count tooltipped tooltipped-n"
+ aria-label="You must be signed in to watch a repository" rel="nofollow">
+ <svg aria-hidden="true" class="octicon octicon-eye" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M8.06 2C3 2 0 8 0 8s3 6 8.06 6C13 14 16 8 16 8s-3-6-7.94-6zM8 12c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4zm2-4c0 1.11-.89 2-2 2-1.11 0-2-.89-2-2 0-1.11.89-2 2-2 1.11 0 2 .89 2 2z"></path></svg>
+ Watch
+ </a>
+ <a class="social-count" href="/pliablepixels/ionic-content-banner/watchers"
+ aria-label="1 user is watching this repository">
+ 1
+ </a>
+
+ </li>
+
+ <li>
+ <a href="/login?return_to=%2Fpliablepixels%2Fionic-content-banner"
+ class="btn btn-sm btn-with-count tooltipped tooltipped-n"
+ aria-label="You must be signed in to star a repository" rel="nofollow">
+ <svg aria-hidden="true" class="octicon octicon-star" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14 7 11.67 11.33 14l-.93-4.74z"></path></svg>
+ Star
+ </a>
+
+ <a class="social-count js-social-count" href="/pliablepixels/ionic-content-banner/stargazers"
+ aria-label="0 users starred this repository">
+ 0
+ </a>
+
+ </li>
+
+ <li>
+ <a href="/login?return_to=%2Fpliablepixels%2Fionic-content-banner"
+ class="btn btn-sm btn-with-count tooltipped tooltipped-n"
+ aria-label="You must be signed in to fork a repository" rel="nofollow">
+ <svg aria-hidden="true" class="octicon octicon-repo-forked" height="16" version="1.1" viewBox="0 0 10 16" width="10"><path d="M8 1a1.993 1.993 0 0 0-1 3.72V6L5 8 3 6V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V6.5l3 3v1.78A1.993 1.993 0 0 0 5 15a1.993 1.993 0 0 0 1-3.72V9.5l3-3V4.72A1.993 1.993 0 0 0 8 1zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3 10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3-10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>
+ Fork
+ </a>
+
+ <a href="/pliablepixels/ionic-content-banner/network" class="social-count"
+ aria-label="23 users are forked this repository">
+ 23
+ </a>
+ </li>
+</ul>
+
+ <h1 class="public ">
+ <svg aria-hidden="true" class="octicon octicon-repo-forked" height="16" version="1.1" viewBox="0 0 10 16" width="10"><path d="M8 1a1.993 1.993 0 0 0-1 3.72V6L5 8 3 6V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V6.5l3 3v1.78A1.993 1.993 0 0 0 5 15a1.993 1.993 0 0 0 1-3.72V9.5l3-3V4.72A1.993 1.993 0 0 0 8 1zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3 10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3-10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>
+ <span class="author" itemprop="author"><a href="/pliablepixels" class="url fn" rel="author">pliablepixels</a></span><!--
+--><span class="path-divider">/</span><!--
+--><strong itemprop="name"><a href="/pliablepixels/ionic-content-banner" data-pjax="#js-repo-pjax-container">ionic-content-banner</a></strong>
+
+ <span class="fork-flag">
+ <span class="text">forked from <a href="/djett41/ionic-content-banner">djett41/ionic-content-banner</a></span>
+ </span>
+</h1>
+
+ </div>
+ <div class="container">
+
+<nav class="reponav js-repo-nav js-sidenav-container-pjax"
+ itemscope
+ itemtype="http://schema.org/BreadcrumbList"
+ role="navigation"
+ data-pjax="#js-repo-pjax-container">
+
+ <span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
+ <a href="/pliablepixels/ionic-content-banner" aria-selected="true" class="js-selected-navigation-item selected reponav-item" data-hotkey="g c" data-selected-links="repo_source repo_downloads repo_commits repo_releases repo_tags repo_branches /pliablepixels/ionic-content-banner" itemprop="url">
+ <svg aria-hidden="true" class="octicon octicon-code" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M9.5 3L8 4.5 11.5 8 8 11.5 9.5 13 14 8 9.5 3zm-5 0L0 8l4.5 5L6 11.5 2.5 8 6 4.5 4.5 3z"></path></svg>
+ <span itemprop="name">Code</span>
+ <meta itemprop="position" content="1">
+</a> </span>
+
+
+ <span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
+ <a href="/pliablepixels/ionic-content-banner/pulls" class="js-selected-navigation-item reponav-item" data-hotkey="g p" data-selected-links="repo_pulls /pliablepixels/ionic-content-banner/pulls" itemprop="url">
+ <svg aria-hidden="true" class="octicon octicon-git-pull-request" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>
+ <span itemprop="name">Pull requests</span>
+ <span class="counter">0</span>
+ <meta itemprop="position" content="3">
+</a> </span>
+
+ <a href="/pliablepixels/ionic-content-banner/projects" class="js-selected-navigation-item reponav-item" data-selected-links="repo_projects new_repo_project repo_project /pliablepixels/ionic-content-banner/projects">
+ <svg class="octicon" aria-hidden="true" version="1.1" width="15" height="16" viewBox="0 0 15 16">
+ <path d="M1 15h13V1H1v14zM15 1v14a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h13a1 1 0 0 1 1 1zm-4.41 11h1.82c.59 0 .59-.41.59-1V3c0-.59 0-1-.59-1h-1.82C10 2 10 2.41 10 3v8c0 .59 0 1 .59 1zm-4-2h1.82C9 10 9 9.59 9 9V3c0-.59 0-1-.59-1H6.59C6 2 6 2.41 6 3v6c0 .59 0 1 .59 1zM2 13V3c0-.59 0-1 .59-1h1.82C5 2 5 2.41 5 3v10c0 .59 0 1-.59 1H2.59C2 14 2 13.59 2 13z"></path>
+ </svg>
+ Projects
+ <span class="counter">0</span>
+</a>
+
+
+ <a href="/pliablepixels/ionic-content-banner/pulse" class="js-selected-navigation-item reponav-item" data-selected-links="pulse /pliablepixels/ionic-content-banner/pulse">
+ <svg aria-hidden="true" class="octicon octicon-pulse" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M11.5 8L8.8 5.4 6.6 8.5 5.5 1.6 2.38 8H0v2h3.6l.9-1.8.9 5.4L9 8.5l1.6 1.5H14V8z"></path></svg>
+ Pulse
+</a>
+ <a href="/pliablepixels/ionic-content-banner/graphs" class="js-selected-navigation-item reponav-item" data-selected-links="repo_graphs repo_contributors /pliablepixels/ionic-content-banner/graphs">
+ <svg aria-hidden="true" class="octicon octicon-graph" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M16 14v1H0V0h1v14h15zM5 13H3V8h2v5zm4 0H7V3h2v10zm4 0h-2V6h2v7z"></path></svg>
+ Graphs
+</a>
+
+</nav>
+
+ </div>
+</div>
+
+<div class="container new-discussion-timeline experiment-repo-nav">
+ <div class="repository-content">
+
+
+<div class="repository-meta js-details-container">
+ <span class="repository-meta-content">
+ <span itemprop="about"> An informational content banner for Ionic applications</span>
+ </span>
+
+</div>
+
+
+<div class="overall-summary overall-summary-bottomless">
+ <div class="stats-switcher-viewport js-stats-switcher-viewport">
+ <div class="stats-switcher-wrapper">
+ <ul class="numbers-summary">
+ <li class="commits">
+ <a data-pjax href="/pliablepixels/ionic-content-banner/commits/master">
+ <svg aria-hidden="true" class="octicon octicon-history" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M8 13H6V6h5v2H8v5zM7 1C4.81 1 2.87 2.02 1.59 3.59L0 2v4h4L2.5 4.5C3.55 3.17 5.17 2.3 7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-.34.03-.67.09-1H.08C.03 7.33 0 7.66 0 8c0 3.86 3.14 7 7 7s7-3.14 7-7-3.14-7-7-7z"></path></svg>
+ <span class="num text-emphasized">
+ 22
+ </span>
+ commits
+ </a>
+ </li>
+ <li>
+ <a data-pjax href="/pliablepixels/ionic-content-banner/branches">
+ <svg aria-hidden="true" class="octicon octicon-git-branch" height="16" version="1.1" viewBox="0 0 10 16" width="10"><path d="M10 5c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v.3c-.02.52-.23.98-.63 1.38-.4.4-.86.61-1.38.63-.83.02-1.48.16-2 .45V4.72a1.993 1.993 0 0 0-1-3.72C.88 1 0 1.89 0 3a2 2 0 0 0 1 1.72v6.56c-.59.35-1 .99-1 1.72 0 1.11.89 2 2 2 1.11 0 2-.89 2-2 0-.53-.2-1-.53-1.36.09-.06.48-.41.59-.47.25-.11.56-.17.94-.17 1.05-.05 1.95-.45 2.75-1.25S8.95 7.77 9 6.73h-.02C9.59 6.37 10 5.73 10 5zM2 1.8c.66 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2C1.35 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2zm0 12.41c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm6-8c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>
+ <span class="num text-emphasized">
+ 1
+ </span>
+ branch
+ </a>
+ </li>
+
+ <li>
+ <a data-pjax href="/pliablepixels/ionic-content-banner/releases">
+ <svg aria-hidden="true" class="octicon octicon-tag" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M7.73 1.73C7.26 1.26 6.62 1 5.96 1H3.5C2.13 1 1 2.13 1 3.5v2.47c0 .66.27 1.3.73 1.77l6.06 6.06c.39.39 1.02.39 1.41 0l4.59-4.59a.996.996 0 0 0 0-1.41L7.73 1.73zM2.38 7.09c-.31-.3-.47-.7-.47-1.13V3.5c0-.88.72-1.59 1.59-1.59h2.47c.42 0 .83.16 1.13.47l6.14 6.13-4.73 4.73-6.13-6.15zM3.01 3h2v2H3V3h.01z"></path></svg>
+ <span class="num text-emphasized">
+ 2
+ </span>
+ releases
+ </a>
+ </li>
+
+ <li>
+ <include-fragment src="/pliablepixels/ionic-content-banner/contributors_size">
+ <a href="/pliablepixels/ionic-content-banner/graphs/contributors">
+ <svg aria-hidden="true" class="octicon octicon-organization" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M16 12.999c0 .439-.45 1-1 1H7.995c-.539 0-.994-.447-.995-.999H1c-.54 0-1-.561-1-1 0-2.634 3-4 3-4s.229-.409 0-1c-.841-.621-1.058-.59-1-3 .058-2.419 1.367-3 2.5-3s2.442.58 2.5 3c.058 2.41-.159 2.379-1 3-.229.59 0 1 0 1s1.549.711 2.42 2.088C9.196 9.369 10 8.999 10 8.999s.229-.409 0-1c-.841-.62-1.058-.59-1-3 .058-2.419 1.367-3 2.5-3s2.437.581 2.495 3c.059 2.41-.158 2.38-1 3-.229.59 0 1 0 1s3.005 1.366 3.005 4"></path></svg>
+ <span class="num text-emphasized"></span>
+ Fetching contributors
+ </a>
+</include-fragment> </li>
+ </ul>
+
+ <div class="repository-lang-stats">
+ <ol class="repository-lang-stats-numbers">
+ <li>
+ <a href="/pliablepixels/ionic-content-banner/search?l=javascript" data-ga-click="Repository, language stats search click, location:repo overview">
+ <span class="color-block language-color" style="background-color:#f1e05a;"></span>
+ <span class="lang">JavaScript</span>
+ <span class="percent">86.5%</span>
+ </a>
+ </li>
+ <li>
+ <a href="/pliablepixels/ionic-content-banner/search?l=css" data-ga-click="Repository, language stats search click, location:repo overview">
+ <span class="color-block language-color" style="background-color:#563d7c;"></span>
+ <span class="lang">CSS</span>
+ <span class="percent">9.6%</span>
+ </a>
+ </li>
+ <li>
+ <a href="/pliablepixels/ionic-content-banner/search?l=html" data-ga-click="Repository, language stats search click, location:repo overview">
+ <span class="color-block language-color" style="background-color:#e44b23;"></span>
+ <span class="lang">HTML</span>
+ <span class="percent">3.9%</span>
+ </a>
+ </li>
+ </ol>
+ </div>
+ </div>
+ </div>
+</div>
+
+ <div class="repository-lang-stats-graph js-toggle-lang-stats" title="Click for language details" data-ga-click="Repository, language bar stats toggle, location:repo overview">
+ <span class="language-color" aria-label="JavaScript 86.5%" style="width:86.5%; background-color:#f1e05a;" itemprop="keywords">JavaScript</span>
+ <span class="language-color" aria-label="CSS 9.6%" style="width:9.6%; background-color:#563d7c;" itemprop="keywords">CSS</span>
+ <span class="language-color" aria-label="HTML 3.9%" style="width:3.9%; background-color:#e44b23;" itemprop="keywords">HTML</span>
+ </div>
+
+
+<div class="file-navigation in-mid-page">
+
+ <div class="select-menu get-repo-select-menu js-menu-container float-right select-menu-modal-right">
+ <button class="btn btn-sm btn-primary select-menu-button js-menu-target"
+ title="Clone or download this repository"
+ type="button" aria-label="Clone or download this repository" tabindex="0" aria-haspopup="true">
+ <span>Clone or download</span>
+ </button>
+
+ <div class="select-menu-modal-holder dropdown-menu-content js-menu-content" aria-hidden="true">
+ <div class="get-repo-modal dropdown-menu dropdown-menu-sw pb-0 js-toggler-container ">
+ <div class="clone-options https-clone-options">
+
+ <h4 class="mb-1">
+ Clone with HTTPS
+ <a class="muted-link" href="https://help.github.com/articles/which-remote-url-should-i-use" target="_blank">
+ <svg aria-hidden="true" class="octicon octicon-question" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M6 10h2v2H6v-2zm4-3.5C10 8.64 8 9 8 9H6c0-.55.45-1 1-1h.5c.28 0 .5-.22.5-.5v-1c0-.28-.22-.5-.5-.5h-1c-.28 0-.5.22-.5.5V7H4c0-1.5 1.5-3 3-3s3 1 3 2.5zM7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7z"></path></svg>
+ </a>
+ </h4>
+ <p class="mb-2 get-repo-decription-text">
+ Use Git or checkout with SVN using the web URL.
+ </p>
+
+ <div class="input-group js-zeroclipboard-container">
+ <input type="text" class="form-control input-monospace input-sm js-zeroclipboard-target js-url-field" value="https://github.com/pliablepixels/ionic-content-banner.git" aria-label="Clone this repository at https://github.com/pliablepixels/ionic-content-banner.git" readonly>
+ <div class="input-group-button">
+ <button aria-label="Copy to clipboard" class="js-zeroclipboard btn btn-sm zeroclipboard-button tooltipped tooltipped-s" data-copied-hint="Copied!" type="button"><svg aria-hidden="true" class="octicon octicon-clippy" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M2 13h4v1H2v-1zm5-6H2v1h5V7zm2 3V8l-3 3 3 3v-2h5v-2H9zM4.5 9H2v1h2.5V9zM2 12h2.5v-1H2v1zm9 1h1v2c-.02.28-.11.52-.3.7-.19.18-.42.28-.7.3H1c-.55 0-1-.45-1-1V4c0-.55.45-1 1-1h3c0-1.11.89-2 2-2 1.11 0 2 .89 2 2h3c.55 0 1 .45 1 1v5h-1V6H1v9h10v-2zM2 5h8c0-.55-.45-1-1-1H8c-.55 0-1-.45-1-1s-.45-1-1-1-1 .45-1 1-.45 1-1 1H3c-.55 0-1 .45-1 1z"></path></svg></button>
+ </div>
+</div>
+
+ </div>
+
+
+ <div class="mt-2">
+
+<a href="/pliablepixels/ionic-content-banner/archive/master.zip"
+ class="btn btn-outline get-repo-btn
+"
+ rel="nofollow"
+ data-ga-click="Repository, download zip, location:repo overview">
+ Download ZIP
+</a>
+
+ </div>
+ </div>
+ </div>
+</div>
+
+
+ <div class="BtnGroup float-right">
+
+ <a href="/pliablepixels/ionic-content-banner/find/master"
+ class="btn btn-sm empty-icon float-right BtnGroup-item"
+ data-pjax
+ data-hotkey="t"
+ data-ga-click="Repository, find file, location:repo overview">
+ Find file
+ </a>
+ </div>
+
+
+<div class="select-menu branch-select-menu js-menu-container js-select-menu float-left">
+ <button class="btn btn-sm select-menu-button js-menu-target css-truncate" data-hotkey="w"
+
+ type="button" aria-label="Switch branches or tags" tabindex="0" aria-haspopup="true">
+ <i>Branch:</i>
+ <span class="js-select-button css-truncate-target">master</span>
+ </button>
+
+ <div class="select-menu-modal-holder js-menu-content js-navigation-container" data-pjax aria-hidden="true">
+
+ <div class="select-menu-modal">
+ <div class="select-menu-header">
+ <svg aria-label="Close" class="octicon octicon-x js-menu-close" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path></svg>
+ <span class="select-menu-title">Switch branches/tags</span>
+ </div>
+
+ <div class="select-menu-filters">
+ <div class="select-menu-text-filter">
+ <input type="text" aria-label="Filter branches/tags" id="context-commitish-filter-field" class="form-control js-filterable-field js-navigation-enable" placeholder="Filter branches/tags">
+ </div>
+ <div class="select-menu-tabs">
+ <ul>
+ <li class="select-menu-tab">
+ <a href="#" data-tab-filter="branches" data-filter-placeholder="Filter branches/tags" class="js-select-menu-tab" role="tab">Branches</a>
+ </li>
+ <li class="select-menu-tab">
+ <a href="#" data-tab-filter="tags" data-filter-placeholder="Find a tag…" class="js-select-menu-tab" role="tab">Tags</a>
+ </li>
+ </ul>
+ </div>
+ </div>
+
+ <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="branches" role="menu">
+
+ <div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
+
+
+ <a class="select-menu-item js-navigation-item js-navigation-open selected"
+ href="/pliablepixels/ionic-content-banner/tree/master"
+ data-name="master"
+ data-skip-pjax="true"
+ rel="nofollow">
+ <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
+ <span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
+ master
+ </span>
+ </a>
+ </div>
+
+ <div class="select-menu-no-results">Nothing to show</div>
+ </div>
+
+ <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="tags">
+ <div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
+
+
+ <a class="select-menu-item js-navigation-item js-navigation-open "
+ href="/pliablepixels/ionic-content-banner/tree/v1.0.1"
+ data-name="v1.0.1"
+ data-skip-pjax="true"
+ rel="nofollow">
+ <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
+ <span class="select-menu-item-text css-truncate-target" title="v1.0.1">
+ v1.0.1
+ </span>
+ </a>
+ <a class="select-menu-item js-navigation-item js-navigation-open "
+ href="/pliablepixels/ionic-content-banner/tree/v1.0.0"
+ data-name="v1.0.0"
+ data-skip-pjax="true"
+ rel="nofollow">
+ <svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5z"></path></svg>
+ <span class="select-menu-item-text css-truncate-target" title="v1.0.0">
+ v1.0.0
+ </span>
+ </a>
+ </div>
+
+ <div class="select-menu-no-results">Nothing to show</div>
+ </div>
+
+ </div>
+ </div>
+</div>
+
+
+ <button type="button" class="btn btn-sm disabled tooltipped tooltipped-n new-pull-request-btn" aria-label="You must be signed in to create a pull request">
+ New pull request
+ </button>
+
+ <div class="breadcrumb">
+
+ </div>
+</div>
+
+
+
+ <div class="branch-infobar">
+ <span class="float-right">
+ <a class="muted-link" href="/pliablepixels/ionic-content-banner/pull/new/master">
+ <svg aria-hidden="true" class="octicon octicon-git-pull-request" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>
+ Pull request
+ </a>
+ <a class="muted-link" href="/pliablepixels/ionic-content-banner/compare">
+ <svg aria-hidden="true" class="octicon octicon-diff" height="16" version="1.1" viewBox="0 0 13 16" width="13"><path d="M6 7h2v1H6v2H5V8H3V7h2V5h1v2zm-3 6h5v-1H3v1zM7.5 2L11 5.5V15c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1h6.5zM10 6L7 3H1v12h9V6zM8.5 0H3v1h5l4 4v8h1V4.5L8.5 0z"></path></svg>
+ Compare
+ </a>
+ </span>
+
+ This branch is 2 commits ahead of djett41:master.
+ </div>
+
+<include-fragment class="commit-tease commit-loader" src="/pliablepixels/ionic-content-banner/tree-commit/eed5b1cb107772d79beb6f34a65d01538f50c444">
+ <div class="blank">
+ <img alt="" class="loader" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32-EAF2F5.gif" width="16" />
+ Fetching latest commit…
+ </div>
+ <div class="loader-error">
+ Cannot retrieve the latest commit at this time.
+ </div>
+</include-fragment>
+
+<include-fragment class="file-wrap" src="/pliablepixels/ionic-content-banner/file-list/master">
+
+ <a href="/pliablepixels/ionic-content-banner/tree/eed5b1cb107772d79beb6f34a65d01538f50c444" class="d-none js-permalink-shortcut" data-hotkey="y">Permalink</a>
+
+ <table class="files js-navigation-container js-active-navigation-container" data-pjax>
+
+
+ <tbody>
+ <tr class="warning include-fragment-error">
+ <td class="icon"><svg aria-hidden="true" class="octicon octicon-alert" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M8.865 1.52c-.18-.31-.51-.5-.87-.5s-.69.19-.87.5L.275 13.5c-.18.31-.18.69 0 1 .19.31.52.5.87.5h13.7c.36 0 .69-.19.86-.5.17-.31.18-.69.01-1L8.865 1.52zM8.995 13h-2v-2h2v2zm0-3h-2V6h2v4z"></path></svg></td>
+ <td class="content" colspan="3">Failed to load latest commit information.</td>
+ </tr>
+
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-directory" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M13 4H7V3c0-.66-.31-1-1-1H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zM6 4H1V3h5v1z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/tree/master/.idea" class="js-navigation-open" id="95b55b1e3a304f4e340394a679893575-a571ab800a86117d8d1b59d80ae35964859488c6" title=".idea">.idea</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-directory" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M13 4H7V3c0-.66-.31-1-1-1H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zM6 4H1V3h5v1z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/tree/master/demo" class="js-navigation-open" id="fe01ce2a7fbac8fafaed7c982a04e229-25035f3c73776ad1316b01e5139d5a3974932dc3" title="demo">demo</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-directory" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M13 4H7V3c0-.66-.31-1-1-1H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zM6 4H1V3h5v1z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/tree/master/dist" class="js-navigation-open" id="2a6d07eef8b10b84129b42424ed99327-f27babf531e9e231ae9f1a501484ccf9797d21c7" title="dist">dist</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-directory" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M13 4H7V3c0-.66-.31-1-1-1H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zM6 4H1V3h5v1z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/tree/master/js" class="js-navigation-open" id="32981a13284db7a021131df49e6cd203-2fe5044bad36cf4ead50a27d76e96437c8faebd5" title="js">js</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-directory" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M13 4H7V3c0-.66-.31-1-1-1H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zM6 4H1V3h5v1z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/tree/master/scss" class="js-navigation-open" id="d711b55165b29776dc8996509be4c9f8-4888ef02353b7695c907007fddb639eaeda51b1f" title="scss">scss</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-directory" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M13 4H7V3c0-.66-.31-1-1-1H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zM6 4H1V3h5v1z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/tree/master/test/unit" class="js-navigation-open" id="877fbbe2538d21acbd5acd35d57e8d66-3cd81fb8dd2f91478cca1235a281d577ad66a7dc" title="This path skips through empty directories"><span class="simplified-path">test/</span>unit</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-text" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M6 5H2V4h4v1zM2 8h7V7H2v1zm0 2h7V9H2v1zm0 2h7v-1H2v1zm10-7.5V14c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1h7.5L12 4.5zM11 5L8 2H1v12h10V5z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/blob/master/.gitignore" class="js-navigation-open" id="a084b794bc0759e7a6b77810e01874f2-bb93d680af6f29ebef270576011c7adbf3804e25" title=".gitignore">.gitignore</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-text" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M6 5H2V4h4v1zM2 8h7V7H2v1zm0 2h7V9H2v1zm0 2h7v-1H2v1zm10-7.5V14c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1h7.5L12 4.5zM11 5L8 2H1v12h10V5z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/blob/master/LICENSE" class="js-navigation-open" id="9879d6db96fd29134fc802214163b95a-e9b3c9751634f8a1a03f69736ebabe48a9e668b6" itemprop="license" title="LICENSE">LICENSE</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-text" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M6 5H2V4h4v1zM2 8h7V7H2v1zm0 2h7V9H2v1zm0 2h7v-1H2v1zm10-7.5V14c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1h7.5L12 4.5zM11 5L8 2H1v12h10V5z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/blob/master/README.md" class="js-navigation-open" id="04c6e90faac2675aa89e2176d2eec7d8-19437ec3d7a4fe07769699f84ce4306c2ede69c5" title="README.md">README.md</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-text" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M6 5H2V4h4v1zM2 8h7V7H2v1zm0 2h7V9H2v1zm0 2h7v-1H2v1zm10-7.5V14c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1h7.5L12 4.5zM11 5L8 2H1v12h10V5z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/blob/master/bower.json" class="js-navigation-open" id="0a08a7565aba4405282251491979bb6b-7e330544c40cfbb329807023c7189be961ca44bd" title="bower.json">bower.json</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-text" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M6 5H2V4h4v1zM2 8h7V7H2v1zm0 2h7V9H2v1zm0 2h7v-1H2v1zm10-7.5V14c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1h7.5L12 4.5zM11 5L8 2H1v12h10V5z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/blob/master/gulpfile.js" class="js-navigation-open" id="b9e12334e9eafd8341a6107dd98510c9-5122394029974f6285b3a3cbbb8633a0ab582608" title="gulpfile.js">gulpfile.js</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-text" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M6 5H2V4h4v1zM2 8h7V7H2v1zm0 2h7V9H2v1zm0 2h7v-1H2v1zm10-7.5V14c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1h7.5L12 4.5zM11 5L8 2H1v12h10V5z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/blob/master/karma.conf.js" class="js-navigation-open" id="a2a3b7b0c9c3b4b93b4aebf4e3ec3cfb-c202222a88def4e97a5755d3abbfb3e54b6a5692" title="karma.conf.js">karma.conf.js</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ <tr class="js-navigation-item">
+ <td class="icon">
+ <svg aria-hidden="true" class="octicon octicon-file-text" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M6 5H2V4h4v1zM2 8h7V7H2v1zm0 2h7V9H2v1zm0 2h7v-1H2v1zm10-7.5V14c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1h7.5L12 4.5zM11 5L8 2H1v12h10V5z"></path></svg>
+ <img alt="" class="spinner" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
+ </td>
+ <td class="content">
+ <span class="css-truncate css-truncate-target"><a href="/pliablepixels/ionic-content-banner/blob/master/package.json" class="js-navigation-open" id="b9cfc7f2cdf78a7f4b91a753d10865a2-36aa036a781c9721fa023b2e4eea183f24161391" title="package.json">package.json</a></span>
+ </td>
+ <td class="message">
+ <span class="css-truncate css-truncate-target">
+ </span>
+ </td>
+ <td class="age">
+ <span class="css-truncate css-truncate-target"></span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+</include-fragment>
+
+
+
+ <div id="readme" class="readme boxed-group clearfix announce instapaper_body md">
+ <h3>
+ <svg aria-hidden="true" class="octicon octicon-book" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M3 5h4v1H3V5zm0 3h4V7H3v1zm0 2h4V9H3v1zm11-5h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9zm2-6v9c0 .55-.45 1-1 1H9.5l-1 1-1-1H2c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1h5.5l1 1 1-1H15c.55 0 1 .45 1 1zm-8 .5L7.5 3H2v9h6V3.5zm7-.5H9.5l-.5.5V12h6V3z"></path></svg>
+ README.md
+ </h3>
+
+ <article class="markdown-body entry-content" itemprop="text"><h1><a id="user-content-ionic-content-banner" class="anchor" href="#ionic-content-banner" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Ionic Content Banner</h1>
+
+<blockquote>
+<p>A Facebook style info/error content banner for the Ionic Framework</p>
+</blockquote>
+
+<h2><a id="user-content-table-of-contents" class="anchor" href="#table-of-contents" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Table of Contents</h2>
+
+<ul>
+<li><a href="#demo">Demo</a></li>
+<li><a href="#setup">Setup</a></li>
+<li><a href="#usage">Usage</a></li>
+<li><a href="#screenshots">Screenshots</a></li>
+</ul>
+
+<h2><a id="user-content-demo" class="anchor" href="#demo" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Demo</h2>
+
+<ul>
+<li>Download my sports news app <strong>SportScoop</strong> on iOS/Android to see it live!! <a href="http://www.sportscoopapp.com">Web Demo</a> | <a href="https://itunes.apple.com/us/app/sportscoop/id1035164619?mt=8">iOS</a> | <a href="https://play.google.com/store/apps/details?id=com.coseur.sportscoop">Android</a></li>
+</ul>
+
+<p><a href="http://www.sportscoopapp.com"><img src="https://camo.githubusercontent.com/f3e6ef7364cc8ee2d69fd8f206916e42fa8f18d1/687474703a2f2f7777772e73706f727473636f6f706170702e636f6d2f6173736574732f73706f727473636f6f705f646f776e6c6f61642e706e67" alt="SportScoop" data-canonical-src="http://www.sportscoopapp.com/assets/sportscoop_download.png" style="max-width:100%;"></a></p>
+
+<ul>
+<li>Watch the Demo video below</li>
+</ul>
+
+<p><a href="http://www.youtube.com/watch?v=O_18dIEbZXM"><img src="https://camo.githubusercontent.com/070697e2e6ff38d6e5bf4dc86fbc0a4c3c7e7dac/687474703a2f2f696d672e796f75747562652e636f6d2f76692f4f5f3138644945625a584d2f302e6a7067" alt="Ionic Content Banner" data-canonical-src="http://img.youtube.com/vi/O_18dIEbZXM/0.jpg" style="max-width:100%;"></a></p>
+
+<ul>
+<li>Download the Demo app on <a href="http://view.ionic.io/">Ionic View</a> with appId: <code>5445a4a4</code></li>
+<li>Run the demo source code by cloning the ionic-content-banner repo, navigating to /demo and running the following</li>
+</ul>
+
+<p>Run the following ommands:</p>
+
+<pre><code>npm install
+bower install
+gulp
+</code></pre>
+
+<h2><a id="user-content-setup" class="anchor" href="#setup" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Setup</h2>
+
+<h4><a id="user-content-install" class="anchor" href="#install" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Install</h4>
+
+<p><code>bower install ionic-content-banner</code></p>
+
+<h4><a id="user-content-jscss-imports-indexhtml" class="anchor" href="#jscss-imports-indexhtml" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>JS/CSS Imports (index.html)</h4>
+
+<p>Include the following JavaScript/CSS file imports in your index.html. Remember to import the ionic libraries first!
+The example below assumes your 3rd party bower dependencies are located in the default bower_components folder.</p>
+
+<pre><code>&lt;link rel="stylesheet" href="bower_components/ionic-content-banner/dist/ionic.content.banner.css"&gt;
+&lt;script src="bower_components/ionic-content-banner/dist/ionic.content.banner.js"&gt;&lt;/script&gt;
+</code></pre>
+
+<h4><a id="user-content-angular-dependency-appjs" class="anchor" href="#angular-dependency-appjs" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Angular Dependency (app.js)</h4>
+
+<p>Add <code>jett.ionic.content.banner</code> as a module dependency of your app module.</p>
+
+<pre><code>angular.module('Demo', ['ionic', 'jett.ionic.content.banner'])
+ .config(function () {..});
+</code></pre>
+
+<h4><a id="user-content-sass-import-mainscss" class="anchor" href="#sass-import-mainscss" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>SASS Import (main.scss)</h4>
+
+<p>Include the <code>scss/ionic.content.banner.scss</code> file at the top of your <code>main.scss</code> file as shown below. Import any
+custom Content Banner scss styles below the ionic and ionic.content.banner scss.</p>
+
+<pre><code>@import
+ "path_to_bower_components/ionic/scss/ionic",
+ "path_to_bower_components/ionic-content-banner/scss/ionic.content.banner";
+</code></pre>
+
+<h2><a id="user-content-usage" class="anchor" href="#usage" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Usage</h2>
+
+<h3><a id="user-content-ioniccontentbanner" class="anchor" href="#ioniccontentbanner" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>$ionicContentBanner</h3>
+
+<p>A service you can inject in your controller to show the Content Banner</p>
+
+<h4><a id="user-content-ioniccontentbannershow" class="anchor" href="#ioniccontentbannershow" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>$ionicContentBanner.show</h4>
+
+<p>Create and show a new Content Banner. A new isolated scope will be created for the Content Banner and the new
+ Content Banner will animate just under the top of the active views ion-content.</p>
+
+<p>@returns {function} <code>close</code> A function which, when called, closes the Content Banner. Make sure you use this to
+ clean up and remove the content banner!!</p>
+
+<p>@param {object} options The options for the content banner. Properties:</p>
+
+<ul>
+<li><p><code>{string=}</code> <code>icon</code></p>
+
+<p>The icon used for the close button. defaults to <code>ion-ios-close-empty</code></p></li>
+<li><p><code>{string=}</code> <code>transition</code></p>
+
+<p>The transition used to animate in the content banner. Supported options are <code>vertical</code> and <code>fade</code>; defaults to
+<code>vertical</code>. The content banner will create a css class <code>content-banner-transition-{transitionVal}</code>, so you can add
+a custom value and then a custom css class to match if you would like to create your own transition.</p></li>
+<li><p><code>{string=}</code> <code>type</code></p>
+
+<p>The type of banner to show. Supported types are <code>info</code> and <code>error</code>; defaults to <code>info</code>. The content banner will
+create a css class <code>content-banner-{typeVal}</code>, so you can add a custom type value and then a custom css class to
+match if you would like to create your own type.</p></li>
+<li><p><code>[String]</code> <code>text</code></p>
+
+<p>A string array that contains the text values to display on the banner. If the array contains more than one value,
+the content banner will cycle through the values of the array at the supplied <code>interval</code> (see below).</p></li>
+<li><p><code>{number=}</code> <code>interval</code></p>
+
+<p>Number of milliseconds between text value changes. Default value is 7000ms (7 seconds).</p></li>
+<li><p><code>{number=}</code> <code>autoClose</code></p>
+
+<p>Number of milliseconds before the content banner automatically closes. This option is disabled by default. </p></li>
+<li><p><code>{boolean=}</code> <code>cancelOnStateChange</code></p>
+
+<p>Whether to cancel the content banner when navigating to a new state. Default value is true.</p></li>
+</ul>
+
+<h2><a id="user-content-screenshots" class="anchor" href="#screenshots" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Screenshots</h2>
+
+<p><a href="/pliablepixels/ionic-content-banner/blob/master/demo/resources/screenshots/ios1.png" target="_blank"><img src="/pliablepixels/ionic-content-banner/raw/master/demo/resources/screenshots/ios1.png" align="left" height="500" width="281" style="max-width:100%;"></a>
+<a href="/pliablepixels/ionic-content-banner/blob/master/demo/resources/screenshots/ios2.png" target="_blank"><img src="/pliablepixels/ionic-content-banner/raw/master/demo/resources/screenshots/ios2.png" align="left" height="500" width="281" style="max-width:100%;"></a></p>
+
+<p><a href="/pliablepixels/ionic-content-banner/blob/master/demo/resources/screenshots/android1.png" target="_blank"><img src="/pliablepixels/ionic-content-banner/raw/master/demo/resources/screenshots/android1.png" align="left" height="500" width="281" style="max-width:100%;"></a>
+<a href="/pliablepixels/ionic-content-banner/blob/master/demo/resources/screenshots/android2.png" target="_blank"><img src="/pliablepixels/ionic-content-banner/raw/master/demo/resources/screenshots/android2.png" align="left" height="500" width="281" style="max-width:100%;"></a></p>
+</article>
+ </div>
+
+
+ </div>
+ <div class="modal-backdrop js-touch-events"></div>
+</div>
+
+
+ </div>
+ </div>
+
+ </div>
+
+ <div class="container site-footer-container">
+ <div class="site-footer" role="contentinfo">
+ <ul class="site-footer-links float-right">
+ <li><a href="https://github.com/contact" data-ga-click="Footer, go to contact, text:contact">Contact GitHub</a></li>
+ <li><a href="https://developer.github.com" data-ga-click="Footer, go to api, text:api">API</a></li>
+ <li><a href="https://training.github.com" data-ga-click="Footer, go to training, text:training">Training</a></li>
+ <li><a href="https://shop.github.com" data-ga-click="Footer, go to shop, text:shop">Shop</a></li>
+ <li><a href="https://github.com/blog" data-ga-click="Footer, go to blog, text:blog">Blog</a></li>
+ <li><a href="https://github.com/about" data-ga-click="Footer, go to about, text:about">About</a></li>
+
+ </ul>
+
+ <a href="https://github.com" aria-label="Homepage" class="site-footer-mark" title="GitHub">
+ <svg aria-hidden="true" class="octicon octicon-mark-github" height="24" version="1.1" viewBox="0 0 16 16" width="24"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path></svg>
+</a>
+ <ul class="site-footer-links">
+ <li>&copy; 2016 <span title="0.12349s from github-fe-651e69d.cp1-iad.github.net">GitHub</span>, Inc.</li>
+ <li><a href="https://github.com/site/terms" data-ga-click="Footer, go to terms, text:terms">Terms</a></li>
+ <li><a href="https://github.com/site/privacy" data-ga-click="Footer, go to privacy, text:privacy">Privacy</a></li>
+ <li><a href="https://github.com/security" data-ga-click="Footer, go to security, text:security">Security</a></li>
+ <li><a href="https://status.github.com/" data-ga-click="Footer, go to status, text:status">Status</a></li>
+ <li><a href="https://help.github.com" data-ga-click="Footer, go to help, text:help">Help</a></li>
+ </ul>
+ </div>
+</div>
+
+
+
+
+
+ <div id="ajax-error-message" class="ajax-error-message flash flash-error">
+ <svg aria-hidden="true" class="octicon octicon-alert" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M8.865 1.52c-.18-.31-.51-.5-.87-.5s-.69.19-.87.5L.275 13.5c-.18.31-.18.69 0 1 .19.31.52.5.87.5h13.7c.36 0 .69-.19.86-.5.17-.31.18-.69.01-1L8.865 1.52zM8.995 13h-2v-2h2v2zm0-3h-2V6h2v4z"></path></svg>
+ <button type="button" class="flash-close js-flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
+ <svg aria-hidden="true" class="octicon octicon-x" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path></svg>
+ </button>
+ You can't perform that action at this time.
+ </div>
+
+
+ <script crossorigin="anonymous" src="https://assets-cdn.github.com/assets/compat-e4d83aa5a60ed31324537822c58c689dbab3943acc78ca2c88fc482479865c7e.js"></script>
+ <script crossorigin="anonymous" src="https://assets-cdn.github.com/assets/frameworks-411d610e4b2b1ffa3d063904534406d08c8351d9ba9b4b3f339bab35e86bb4f3.js"></script>
+ <script async="async" crossorigin="anonymous" src="https://assets-cdn.github.com/assets/github-877cf6bfb710d9ccb14e4a6841ffe0fe6b52fec0f230b1f594ed779dc6ba0f94.js"></script>
+
+
+
+
+
+
+ <div class="js-stale-session-flash stale-session-flash flash flash-warn flash-banner d-none">
+ <svg aria-hidden="true" class="octicon octicon-alert" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M8.865 1.52c-.18-.31-.51-.5-.87-.5s-.69.19-.87.5L.275 13.5c-.18.31-.18.69 0 1 .19.31.52.5.87.5h13.7c.36 0 .69-.19.86-.5.17-.31.18-.69.01-1L8.865 1.52zM8.995 13h-2v-2h2v2zm0-3h-2V6h2v4z"></path></svg>
+ <span class="signed-in-tab-flash">You signed in with another tab or window. <a href="">Reload</a> to refresh your session.</span>
+ <span class="signed-out-tab-flash">You signed out in another tab or window. <a href="">Reload</a> to refresh your session.</span>
+ </div>
+ <div class="facebox" id="facebox" style="display:none;">
+ <div class="facebox-popup">
+ <div class="facebox-content" role="dialog" aria-labelledby="facebox-header" aria-describedby="facebox-description">
+ </div>
+ <button type="button" class="facebox-close js-facebox-close" aria-label="Close modal">
+ <svg aria-hidden="true" class="octicon octicon-x" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path></svg>
+ </button>
+ </div>
+</div>
+
+ </body>
+</html>
+
diff --git a/www/templates/devoptions.html b/www/templates/devoptions.html
index 98a9dcef..b206a559 100644
--- a/www/templates/devoptions.html
+++ b/www/templates/devoptions.html
@@ -11,7 +11,7 @@
</ion-nav-buttons>
- <ion-content class="padding">
+ <ion-content scroll-sista delegate-handle="none" overflow-scroll="false" mouse-wheel-scroll >
<div class="list list-inset">
<span style="color:rgb(100,100,100)">
<i class="ion-android-settings" style="font-size:150%"></i> {{'kDeveloperOptionsFor'|translate}} {{loginData.serverName}}
diff --git a/www/templates/events-date-time-filter.html b/www/templates/events-date-time-filter.html
index 84e39ea2..af97ad60 100644
--- a/www/templates/events-date-time-filter.html
+++ b/www/templates/events-date-time-filter.html
@@ -2,7 +2,7 @@
- <ion-content>
+ <ion-content scroll-sista delegate-handle="none" overflow-scroll="false" mouse-wheel-scroll >
<label class="item item-input">
<span class="input-label">{{'kFromDate'|translate}}:</span>
<input type="date" ng-model="$root.fromDate" max={{today}}>
diff --git a/www/templates/events.html b/www/templates/events.html
index 485cdc0d..8ea421a3 100644
--- a/www/templates/events.html
+++ b/www/templates/events.html
@@ -1,5 +1,6 @@
<ion-view cache-view="false">
+
<ion-nav-title>{{scrollPosition();}}</ion-nav-title>
<ion-nav-buttons side="left">
<button class="button button-icon button-clear ion-navicon" ng-click="openMenu()">
@@ -27,10 +28,12 @@
</div>
-
- <ion-content ng-cloak on-tap="tapped();" delegate-handle="mainScroll" has-subheader="true" overflow-scroll="false" mouse-wheel-scroll>
-
-
+
+ <!-- collection repeat forces js scrolling, thing to remember -->
+
+ <ion-content scroll-sista ng-cloak on-tap="tapped();" delegate-handle="mainScroll" mouse-wheel-scroll>
+ <!-- needed for header-shrink so first item doesn't go below header-->
+ <!-- <div style="height: 64px;"></div>-->
<!-- lets make sure the events list is not empty as collection repeat needs height -->
<div ng-if="!eventsBeingLoaded">
diff --git a/www/templates/eventserversettings.html b/www/templates/eventserversettings.html
index 6a3b7d90..e6ec4614 100644
--- a/www/templates/eventserversettings.html
+++ b/www/templates/eventserversettings.html
@@ -12,7 +12,7 @@
<button class="button button-clear" ng-click="saveItems()">{{'kSave'|translate}}</button>
</ion-nav-buttons>
- <ion-content padding="true">
+ <ion-content scroll-sista delegate-handle="none" overflow-scroll="false" mouse-wheel-scroll >
<div class="list list-inset">
{{'kEventServerConfig1' | translate }}
diff --git a/www/templates/help.html b/www/templates/help.html
index 169d1abe..fffdf2d1 100644
--- a/www/templates/help.html
+++ b/www/templates/help.html
@@ -1,4 +1,4 @@
-<ion-view view-title="Help">
+<ion-view cache-view="false" view-title="Help">
<ion-nav-buttons side="left">
<button class="button button-icon button-clear ion-navicon" ng-click="openMenu()"></button>
@@ -8,7 +8,7 @@
</ion-nav-buttons>
- <ion-content class="padding" overflow-scroll="false" ng-cloak>
+ <ion-content class="padding" scroll-sista delegate-handle="none" overflow-scroll="false" mouse-wheel-scroll>
<p><small>{{$root.appName}} v{{zmAppVersion}}</small></p>
<div class="list">
diff --git a/www/templates/log.html b/www/templates/log.html
index 7bbb022a..e53f00aa 100644
--- a/www/templates/log.html
+++ b/www/templates/log.html
@@ -22,7 +22,7 @@
</div>
</ion-nav-buttons>
- <ion-content class="padding">
+ <ion-content scroll-sista delegate-handle="none" overflow-scroll="false" mouse-wheel-scroll>
<b>{{$root.appName}} {{'kVersion'|translate}}: {{zmAppVersion}}</b><br/>
<!-- don't indent here -- its a pre-->
<pre>{{log.logString}}</pre>
diff --git a/www/templates/login.html b/www/templates/login.html
index 054aaf49..738c1c46 100644
--- a/www/templates/login.html
+++ b/www/templates/login.html
@@ -13,7 +13,7 @@
<button class="button button-clear" ng-click="saveItems()">Save</button>
</ion-nav-buttons>
- <ion-content padding="true">
+ <ion-content scroll-sista delegate-handle="none" overflow-scroll="false" mouse-wheel-scroll >
<div class="item item-input-inset">
{{'kServerName' | translate }}:&nbsp;
diff --git a/www/templates/monitors.html b/www/templates/monitors.html
index 6c23e6c7..a9168c09 100644
--- a/www/templates/monitors.html
+++ b/www/templates/monitors.html
@@ -1,4 +1,5 @@
<ion-view view-title="{{'kMonitors' | translate}}" cache-view="false">
+
<ion-nav-buttons side="left">
<button class="button button-icon button-clear ion-navicon" ng-click="openMenu()">
</button>
@@ -6,8 +7,9 @@
<button data-badge="{{$root.alarmCount}}" class="animated infinite tada button button-icon button-clear ion-ios-bell notification-badge" ng-click="handleAlarms();" ng-if="$root.isAlarm"></button>
</ion-nav-buttons>
+
- <ion-content overflow-scroll="false">
+ <ion-content scroll-sista delegate-handle="none" overflow-scroll="false" mouse-wheel-scroll >
<ion-refresher pulling-text="Pull to reload Monitors..." spinner="bubbles" on-refresh="doRefresh()">
</ion-refresher>
<div style="float:right;margin-top:3px;margin-right:8px;">
diff --git a/www/templates/montage-history.html b/www/templates/montage-history.html
index f0e4a4a5..eb751105 100644
--- a/www/templates/montage-history.html
+++ b/www/templates/montage-history.html
@@ -21,7 +21,7 @@
</ion-nav-buttons>
- <ion-content has-bouncing="false" style="background-color:#444444">
+ <ion-content scroll-sista has-bouncing="false" style="background-color:#444444" delegate-handle="montage-delegate" overflow-scroll="false" mouse-wheel-scroll>
diff --git a/www/templates/montage-history2.html b/www/templates/montage-history2.html
new file mode 100644
index 00000000..69fb2bb6
--- /dev/null
+++ b/www/templates/montage-history2.html
@@ -0,0 +1,230 @@
+<ion-view view-title="{{'kEventMontage' | translate}}" cache-view="false" hide-nav-bar="{{minimal}}">
+ <ion-nav-buttons side="left">
+ <button class="button button-icon button-clear ion-navicon" ng-click="openMenu()"></button>
+
+ <button class="button button-icon button-clear ion-arrow-move" ng-click="dragToggle();">&nbsp;
+ </button>
+
+ <button data-badge="{{$root.alarmCount}}" class="animated infinite tada button button-icon button-clear ion-ios-bell notification-badge" ng-click="handleAlarms();" ng-if="$root.isAlarm"></button>
+ </ion-nav-buttons>
+
+
+
+ <ion-nav-buttons side="right">
+
+ <button class="button button-icon button-clear ion-loop" ng-click="resetSizes();">&nbsp;
+ </button>
+
+ <button class="button button-icon button-clear ion-plus-round" ng-click="sliderChanged(1);">&nbsp;
+ </button>
+
+ <button class="button button-icon button-clear ion-minus-round" ng-click="sliderChanged(-1);">&nbsp;
+ </button>
+
+ <button data-badge="{{$root.alarmCount}}" class="animated infinite tada button button-icon button-clear ion-ios-bell notification-badge" ng-click="handleAlarms();" ng-if="$root.isAlarm"></button>
+
+
+
+ </ion-nav-buttons>
+
+ <ion-content scroll-sista has-bouncing="false" style="background-color:#444444" delegate-handle="none" overflow-scroll="false" mouse-wheel-scroll>
+
+
+
+ <div class="timeline_text" ion-datetime-picker title="From" am-pm={{!loginData.use24hr}} ng-model="datetimeValueFrom.value" ng-change="dateChanged()">
+ <b>{{'kFrom' | translate}} : </b>{{datetimeValueFrom.value | date: timeFormat}}
+ </div>
+
+ <div class="timeline_text" ion-datetime-picker am-pm={{!loginData.use24hr}} ng-model="datetimeValueTo.value" ng-change="dateChanged()">
+ <b>{{'kTo' | translate}}: </b>{{datetimeValueTo.value | date: timeFormat}} @ {{sliderVal.rate}}x
+ <div ng-if="$root.platformOS != 'ios'">({{'kChromeMax' | translate}})</div>
+ </div>
+
+
+
+
+ <div class="grid" id="mygrid">
+ <div class="grid-sizer grid-item-10"></div>
+
+
+
+
+ <!-- <span ng-repeat="monitor in MontageMonitors|limitTo: monLimit" ng- -->
+ <span ng-repeat="monitor in MontageMonitors | onlyEnabled |limitTo: monLimit">
+
+
+
+
+ <div ng-if="$root.authSession!='undefined'">
+ <div ng-if = "monitor.Monitor.eventUrl == 'img/noevent.png' && !sliderVal.hideNoEvents">
+
+ <!-- make sure we don't use id here
+ -- or we lose the handle for cleanup forever!-->
+ <div class="grid-item grid-item-{{monitor.Monitor.gridScale}} " data-item-id="{{monitor.Monitor.Id}}" data-item-size="{{monitor.Monitor.gridScale}}" data-item-listdisplay="{{monitor.Monitor.listDisplay}}" >
+ <figure height="{{Monitor.monitor.height}}" width="{{Monitor.monitor.width}}" class="{{dragBorder}}" ng-show=" monitor.Monitor.listDisplay!='noshow'">
+
+ <img class="{{monitor.Monitor.selectStyle}}" image-spinner-src="{{monitor.Monitor.eventUrl}}" image-spinner-loader="lines" on-tap="!isDragabillyOn?noop():toggleSelectItem($index)" />
+
+
+ <figcaption class="normal-figcaption" >
+
+ &nbsp;<i class="ion-ios-videocam"></i>
+ {{monitor.Monitor.Name}}&nbsp;
+
+ </figcaption>
+
+
+
+ </figure>
+ </div>
+ </div>
+
+ <div ng-if = "monitor.Monitor.eventUrl != 'img/noevent.png' && monitor.Monitor.connKey !=''">
+
+ <div class="grid-item grid-item-{{monitor.Monitor.gridScale}} " data-item-id="{{monitor.Monitor.Id}}" data-item-size="{{monitor.Monitor.gridScale}}" data-item-listdisplay="{{monitor.Monitor.listDisplay}}" >
+ <figure height="{{Monitor.monitor.height}}" width="{{Monitor.monitor.width}}" class="{{dragBorder}}" ng-show=" monitor.Monitor.listDisplay!='noshow'">
+
+ <img class="{{monitor.Monitor.selectStyle}}" image-spinner-src="{{monitor.Monitor.eventUrl}}{{$root.authSession}}" image-spinner-loader="lines" on-tap="!isDragabillyOn?togglePause(monitor.Monitor.Id):toggleSelectItem($index)" />
+
+
+ <figcaption class="normal-figcaption" >
+
+ &nbsp;<i class="ion-ios-videocam"></i> <span style="background-color:red;color:#fff" ng-if="monitor.Monitor.isPaused">&nbsp;<i class="ion-pause"></i>&nbsp;</span>
+ {{monitor.Monitor.Name}}&nbsp;
+
+ <div ng-if="sliderVal.showTimeline && $root.runMode!='lowbw'" style="white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-size:9px"
+ class="header-event-id" id="{{monitor.Monitor.Id}}-timeline">
+ <i class="ion-clock"></i>
+ {{prettifyDateTimeFirst(monitor.Monitor.eventUrlTime)}} ({{humanizeTime(monitor.Monitor.eventUrlTime)}})&nbsp;
+ </div>
+
+ </figcaption>
+
+
+
+ </figure>
+ </div>
+
+
+
+
+
+ <!--
+ <div ng-if="sliderVal.showTimeline && $root.runMode!='lowbw'" style=" position:absolute; bottom:15px; right:0%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;"
+ class="header-event-id" id="{{monitor.Monitor.Id}}-timeline">
+ &nbsp;<i class="ion-clock"></i>
+ {{prettifyDate(monitor.Monitor.eventUrlTime)}}&nbsp;
+ </div>
+ -->
+
+
+ <!--<div ng-if="!monitor.isPaused"
+ style="position:absolute; bottom:35px; right:0%;white-space:nowrap;overflow:hidden;" class="header-event-id">paused
+ </div>-->
+
+
+ </div>
+ </div> <!-- valid auth session &!background -->
+
+ <div ng-if="!$root.authSession=='undefined' || isBackground()">
+ <img image-spinner-src="img/pausevideo.png"
+ />
+
+ </div>
+ </span>
+ </div>
+
+ <ion-item ng-show="!MontageMonitors.length">
+ {{'kNoMonitors' | translate }}
+ </ion-item>
+
+
+
+ </ion-content>
+
+ <div class="bwmode" ng-if="$root.runMode=='lowbw'">
+ {{ 'kLowBWDisplay' | translate }}
+ </div>
+
+ <div ng-show="minimal">
+ <nav mfb-menu position="br" effect="zoomin" label="collapse" active-icon="ion-chevron-down" resting-icon="ion-chevron-up" toggling-method="click">
+
+ <button mfb-button icon="ion-arrow-expand" label="increase size" ng-click="changeSize(-1)">
+ </button>
+ <button mfb-button icon="ion-arrow-shrink" label="decrease size" ng-click="changeSize(1)">
+ </button>
+ <button mfb-button icon="ion-refresh" label="refresh" ng-click="reloadView();">
+ </button>
+ <button mfb-button icon="ion-close" label="exit full screen" ng-click="switchMinimal()">
+ </button>
+ </nav>
+
+ <span class="modal-alarm-badge">
+ <a data-badge="{{$root.alarmCount}}" class="animated infinite tada button icon ion-ios-bell notification-badge button-assertive"
+ ng-click="handleAlarmsWhileMinimized();" ng-if="$root.isAlarm"></a>
+ </span>
+
+ </div>
+
+ <ion-pull-up-footer class="zmPullup" on-expand="footerExpand()" on-minimize="footerCollapse()" on-collapse="footerCollapse()" initial-state="minimized" default-behavior="expand">
+ <ion-pull-up-handle width="100" height="25" toggle="ion-chevron-up ion-chevron-down" style="border-radius: 25px 25px 0 0">
+ <i class="icon ion-chevron-up"></i>
+ </ion-pull-up-handle>
+ <ion-pull-up-bar>
+ <h1 class="title" ion-pull-up-trigger>{{'kEventMontage' | translate}}</h1>
+ </ion-pull-up-bar>
+ <ion-pull-up-content scroll="true">
+
+
+ <div class="list list-inset">
+
+
+ <ion-toggle ng-model="sliderVal.hideNoEvents" ng-checked="{{sliderVal.hideNoEvents}}" toggle-class="toggle-dark"><span class="item-text-wrap">{{'kHideMonsWithoutEvents' | translate}}</span></ion-toggle>
+
+
+
+
+ <!--<div class="item item-divider" ion-datetime-picker ng-model="datetimeValueFrom.value">
+ Tap to change: {{datetimeValueFrom.value| date: "yyyy-MMM-dd hh:mma"}}
+ </div>-->
+
+
+
+
+
+ <div class="row">
+ <div class="col col-75">
+ <br/>
+ <div style="width:90%;color:black;">
+ <input ng-model="sliderVal.rate" type="text" id="mySlider6" slider options="slider_modal_options_rate" />
+ </div>
+ <br/>
+ </div>
+ <div class="col col-25" style="background-color:#AEA8D3;text-align:center">
+ {{'kSpeed' | translate }}
+ </div>
+ </div>
+
+ <div class="item item-divider">{{'kTimeline' | translate}}</div>
+
+ <ion-item>
+ <div ion-datetime-picker title="From" am-pm={{!loginData.use24hr}} ng-model="datetimeValueFrom.value">
+ <b>{{'kFrom' | translate }}: </b>{{datetimeValueFrom.value | date: timeFormat}}
+ </div>
+ </ion-item>
+
+ <ion-item>
+ <div ion-datetime-picker am-pm={{!loginData.use24hr}} ng-model="datetimeValueTo.value">
+ <b>{{'kTo' | translate}}: </b>{{datetimeValueTo.value | date: timeFormat}}
+ </div>
+ </ion-item>
+
+ </div>
+ </ion-pull-up-content>
+ </ion-pull-up-footer>
+
+
+
+
+
+</ion-view> \ No newline at end of file
diff --git a/www/templates/montage.html b/www/templates/montage.html
index f79db1ef..b442f998 100644
--- a/www/templates/montage.html
+++ b/www/templates/montage.html
@@ -33,7 +33,7 @@
</ion-nav-buttons>
- <ion-content ng-cloak has-bouncing="false" style="background-color:#444444" delegate-handle="montage-delegate" >
+ <ion-content scroll-sista ng-cloak has-bouncing="false" style="background-color:#444444" delegate-handle="montage-delegate" overflow-scroll="false" >
diff --git a/www/templates/news.html b/www/templates/news.html
index 3edb2a16..f4a87e4f 100644
--- a/www/templates/news.html
+++ b/www/templates/news.html
@@ -8,7 +8,7 @@
</ion-nav-buttons>
- <ion-content >
+ <ion-content scroll-sista delegate-handle="none" overflow-scroll="false" mouse-wheel-scroll >
<div class="list">
<span ng-repeat = "post in newsItems">
diff --git a/www/templates/state.html b/www/templates/state.html
index 5763c284..bf0cda4a 100644
--- a/www/templates/state.html
+++ b/www/templates/state.html
@@ -4,7 +4,7 @@
<button class="button button-icon button-clear ion-navicon" ng-click="openMenu()"></button>
<button data-badge="{{$root.alarmCount}}" class="animated infinite tada button button-icon button-clear ion-ios-bell notification-badge" ng-click="handleAlarms();" ng-if="$root.isAlarm"></button>
</ion-nav-buttons>
- <ion-content>
+ <ion-content scroll-sista delegate-handle="none" overflow-scroll="false" mouse-wheel-scroll>
<ion-refresher pulling-text="{{'kPullToReload' | translate}}..."
spinner="bubbles" on-refresh="doRefresh()"></ion-refresher>
<ion-list>
diff --git a/www/templates/timeline.html b/www/templates/timeline.html
index 2f8e6481..e443e705 100644
--- a/www/templates/timeline.html
+++ b/www/templates/timeline.html
@@ -23,7 +23,7 @@
</ion-nav-buttons>
<!--<ion-content data-tap-disabled="true">-->
- <ion-content>
+ <ion-content scroll-sista delegate-handle="none" overflow-scroll="false" mouse-wheel-scroll>
<div style="padding-left:15px; font-size:10px; color:grey">
{{prettify(fromDate)}} - {{prettify(toDate)}} ({{'kTimelineOnlyDisplaying1' | translate:translationData}})
diff --git a/www/templates/wizard.html b/www/templates/wizard.html
index ca2daf6a..3d20c522 100644
--- a/www/templates/wizard.html
+++ b/www/templates/wizard.html
@@ -86,6 +86,7 @@
<wz-step wz-title="3" >
+ <br/><br/>
<h4>{{'kWizResults' | translate}}</h4>
<span ng-if="wizard.portalValidText" style="color:{{wizard.portalColor}};"><i ng-class="wizard.portalColor=='#16a085' ? 'ion-checkmark-circled':'ion-close-circled'"></i>&nbsp;{{wizard.portalValidText}}<br/></span>