From 48b340356832f95064dc626d4c659b217addf946 Mon Sep 17 00:00:00 2001 From: Igor Zhukov Date: Wed, 5 Feb 2014 23:44:31 +0400 Subject: [PATCH] Added audio support, big message splitting, improved styles --- app/css/app.css | 158 ++- app/img/icons/IconsetW.png | Bin 6884 -> 9132 bytes app/img/icons/IconsetW_1x.png | Bin 3311 -> 4320 bytes app/img/icons/Location_Active.png | Bin 3238 -> 0 bytes app/img/icons/VideoIcon.png | Bin 321 -> 0 bytes app/index.html | 16 +- app/js/app.js | 2 +- app/js/controllers.js | 6 +- app/js/directives.js | 29 +- app/js/filters.js | 4 +- app/js/lib/mtproto.js | 23 +- app/js/services.js | 131 ++- app/js/util.js | 6 +- app/partials/dialog.html | 41 +- app/partials/im.html | 2 +- app/partials/message.html | 47 +- .../ui-bootstrap-custom-0.10.0.js | 990 ++++++++++++++++ .../ui-bootstrap-custom-0.10.0.min.js | 8 + .../ui-bootstrap/ui-bootstrap-custom-0.7.0.js | 332 ------ .../ui-bootstrap-custom-0.7.0.min.js | 1 - .../ui-bootstrap-custom-tpls-0.10.0.js | 1024 +++++++++++++++++ .../ui-bootstrap-custom-tpls-0.10.0.min.js | 8 + .../ui-bootstrap-custom-tpls-0.7.0.js | 353 ------ .../ui-bootstrap-custom-tpls-0.7.0.min.js | 1 - 24 files changed, 2386 insertions(+), 796 deletions(-) delete mode 100644 app/img/icons/Location_Active.png delete mode 100755 app/img/icons/VideoIcon.png create mode 100644 app/vendor/ui-bootstrap/ui-bootstrap-custom-0.10.0.js create mode 100644 app/vendor/ui-bootstrap/ui-bootstrap-custom-0.10.0.min.js delete mode 100644 app/vendor/ui-bootstrap/ui-bootstrap-custom-0.7.0.js delete mode 100644 app/vendor/ui-bootstrap/ui-bootstrap-custom-0.7.0.min.js create mode 100644 app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.js create mode 100644 app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.min.js delete mode 100644 app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.7.0.js delete mode 100644 app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.7.0.min.js diff --git a/app/css/app.css b/app/css/app.css index 27db35a4..3565a435 100644 --- a/app/css/app.css +++ b/app/css/app.css @@ -400,8 +400,8 @@ fieldset[disabled] .btn-tg.active { .im_dialogs_search_field { font-size: 12px; line-height: normal; - background: #F2F2F2 url(../img/icons/IconsetW.png) -6px -205px no-repeat; - background-size: 42px 280px; + background: #F2F2F2 url(../img/icons/IconsetW.png?1) -6px -205px no-repeat; + background-size: 42px 430px; border: 1px solid #F2F2F2; border-radius: 3px; padding: 6px 6px 6px 30px; @@ -409,7 +409,7 @@ fieldset[disabled] .btn-tg.active { margin: 0; } .is_1x .im_dialogs_search_field { - background-image: url(../img/icons/IconsetW_1x.png); + background-image: url(../img/icons/IconsetW_1x.png?1); } .im_dialogs_search_field:focus, @@ -425,12 +425,12 @@ fieldset[disabled] .btn-tg.active { width: 13px; height: 13px; vertical-align: text-top; - background: url(../img/icons/IconsetW.png) -15px -192px no-repeat; - background-size: 42px 280px; + background: url(../img/icons/IconsetW.png?1) -15px -192px no-repeat; + background-size: 42px 430px; opacity: 0.6; } .is_1x .im_dialogs_search_clear { - background-image: url(../img/icons/IconsetW_1x.png); + background-image: url(../img/icons/IconsetW_1x.png?1); } .im_dialogs_search_clear:hover { opacity: 1; @@ -521,9 +521,37 @@ a.im_dialog:hover .im_dialog_message_text { color: #428bca; background-color: #fff; } + +.im_dialog_unread { + background: #c1d6e5; + display: inline-block; + float: right; + width: 10px; + height: 10px; + border-radius: 7px; + overflow: hidden; + /*position: absolute;*/ + margin: 8px 0 0; +} + +a.im_dialog:hover .im_dialog_unread { + background: #a3c0d4; +} +.active .im_dialog_unread { + background-color: #a4c4dd; +} + + .im_dialog_date { - font-size: 0.8em; - visibility: hidden; + color: #b3b3b3; + font-size: 0.85em; +} +a.im_dialog:hover .im_dialog_date { + color: #91a6ba; +} +.active .im_dialog_date, +.active a.im_dialog:hover .im_dialog_date { + color: #b8d1e4; } .im_dialog_service { @@ -777,18 +805,22 @@ div.im_message_video_thumb { display: inline-block; top: 50%; left: 50%; - margin-left: -5px; - margin-top: -7px; - width: 10px; - height: 14px; + margin-left: -8px; + margin-top: -10px; + width: 15px; + height: 19px; - background: url(../img/icons/Location_Active.png?1) 0 0 no-repeat; - background-size: 10px 14px; + background: url(../img/icons/IconsetW.png?1) -14px -389px no-repeat; + background-size: 42px 430px; +} +.is_1x .icon-geo-point { + background-image: url(../img/icons/IconsetW_1x.png?1); } .im_message_document, +.im_message_audio, .im_message_upload_file { margin-top: 3px; border-radius: 3px; @@ -797,20 +829,24 @@ div.im_message_video_thumb { width: 340px; } -.icon-document { +.icon-document, +.icon-photo, +.icon-video { display: block; float: left; width: 38px; height: 38px; vertical-align: text-top; - background: #F2F2F2 url(../img/icons/IconsetW.png) -2px -229px no-repeat; - background-size: 42px 280px; + background: #F2F2F2 url(../img/icons/IconsetW.png?1) -2px -229px no-repeat; + background-size: 42px 430px; border-radius: 3px; margin-right: 10px; } -.is_1x .icon-document { - background-image: url(../img/icons/IconsetW_1x.png); +.is_1x .icon-document, +.is_1x .icon-photo, +.is_1x .icon-video { + background-image: url(../img/icons/IconsetW_1x.png?1); } .im_message_document_info { @@ -840,6 +876,53 @@ div.im_message_video_thumb { padding-left: 2px; } +.icon-audio { + display: block; + float: left; + width: 38px; + height: 38px; + vertical-align: text-top; + + background: #F2F2F2 url(../img/icons/IconsetW.png?1) -2px -277px no-repeat; + background-size: 42px 430px; + border-radius: 3px; + margin-right: 10px; +} +.is_1x .icon-audio { + background-image: url(../img/icons/IconsetW_1x.png?1); +} + +.im_message_audio_info { + float: left; + width: 292px; +} +.im_message_audio_name_wrap { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + width: 290px; + padding: 0 0 1px; +} +.im_message_audio_name { + color: #222; + display: inline-block; + font-weight: bold; + max-width: 200px; + overflow: hidden; + vertical-align: text-top; + white-space: nowrap; + text-overflow: ellipsis; +} +.im_message_audio_duration, +.im_message_audio_size { + color: #999; + padding-left: 2px; +} +.im_message_audio_info audio { + height: 38px; + line-height: 38px; +} + .im_message_upload_progress_wrap, .im_message_download_progress_wrap { margin-top: 5px; @@ -906,9 +989,6 @@ div.im_message_video_thumb { margin-left: -27px; margin-top: 14px; opacity: 0; - - -webkit-transition: opacity ease-in-out 0.15s; - transition: opacity ease-in-out 0.15s; } .icon-message-status-unread { opacity: 1.0; @@ -916,9 +996,13 @@ div.im_message_video_thumb { .icon-message-status-pending { opacity: 0.5; } -/*.icon-message-status-done { - opacity: 0; -}*/ +.icon-message-status-error { + background: #da564d; + opacity: 0.85; +} +.icon-message-status-error:hover { + opacity: 1; +} .icon-message-status-tick { /*display: inline-block;*/ @@ -1036,12 +1120,12 @@ textarea.im_message_field { width: 19px; height: 23px; vertical-align: text-top; - background: url(../img/icons/IconsetW.png) -12px -68px no-repeat; - background-size: 42px 280px; + background: url(../img/icons/IconsetW.png?1) -12px -68px no-repeat; + background-size: 42px 430px; opacity: 0.8; } .is_1x .icon-paperclip { - background-image: url(../img/icons/IconsetW_1x.png); + background-image: url(../img/icons/IconsetW_1x.png?1); } .im_attach:hover .icon-paperclip { opacity: 1; @@ -1065,12 +1149,12 @@ textarea.im_message_field { width: 23px; height: 23px; vertical-align: text-top; - background: url(../img/icons/IconsetW.png) -10px -4px no-repeat; - background-size: 42px 280px; + background: url(../img/icons/IconsetW.png?1) -10px -4px no-repeat; + background-size: 42px 430px; opacity: 0.8; } .is_1x .icon-emoji { - background-image: url(../img/icons/IconsetW_1x.png); + background-image: url(../img/icons/IconsetW_1x.png?1); } .im_emoji_btn:hover .icon-emoji { opacity: 1; @@ -1113,12 +1197,12 @@ textarea.im_message_field { width: 25px; height: 21px; vertical-align: text-top; - background: url(../img/icons/IconsetW.png) -9px -132px no-repeat; - background-size: 42px 280px; + background: url(../img/icons/IconsetW.png?1) -9px -132px no-repeat; + background-size: 42px 430px; opacity: 0.8; } .is_1x .icon-camera { - background-image: url(../img/icons/IconsetW_1x.png); + background-image: url(../img/icons/IconsetW_1x.png?1); } .im_media_attach:hover .icon-camera { opacity: 1; @@ -1360,14 +1444,14 @@ img.img_fullsize { } .emoji-menu-tail { - background: url(../img/icons/IconsetW.png) -14px -268px no-repeat; - background-size: 42px 280px; + background: url(../img/icons/IconsetW.png?1) -14px -268px no-repeat; + background-size: 42px 430px; width: 14px; height: 7px; margin: 0 83px; } .is_1x .emoji-menu-tail { - background-image: url(../img/icons/IconsetW_1x.png); + background-image: url(../img/icons/IconsetW_1x.png?1); } diff --git a/app/img/icons/IconsetW.png b/app/img/icons/IconsetW.png index 2c33d55f1cc640ee7e14b1c833eb58ba571af042..15fa913d6777604a8abde63c4f034655f8359cbd 100644 GIT binary patch literal 9132 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 Csv!;l literal 6884 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 QsQ@~f4>jK2w}1YB02~nz_W%F@ diff --git a/app/img/icons/IconsetW_1x.png b/app/img/icons/IconsetW_1x.png index f82ec58ea04291c3b261b582745bf919327f5fa8..7d7b5c4fc7e686b44b06f1d169a6254c2f83612f 100644 GIT binary patch delta 4286 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 - + @@ -40,7 +40,7 @@ - + @@ -48,14 +48,14 @@ - + - - - - + + + + - + diff --git a/app/js/app.js b/app/js/app.js index c3f8ddb5..f4dc5422 100644 --- a/app/js/app.js +++ b/app/js/app.js @@ -57,7 +57,7 @@ config(['$locationProvider', '$routeProvider', '$compileProvider', function($loc // $locationProvider.html5Mode(true); $routeProvider.when('/', {templateUrl: 'partials/welcome.html?3', controller: 'AppWelcomeController'}); $routeProvider.when('/login', {templateUrl: 'partials/login.html?4', controller: 'AppLoginController'}); - $routeProvider.when('/im', {templateUrl: 'partials/im.html?11', controller: 'AppIMController', reloadOnSearch: false}); + $routeProvider.when('/im', {templateUrl: 'partials/im.html?12', controller: 'AppIMController', reloadOnSearch: false}); $routeProvider.otherwise({redirectTo: '/'}); }]); diff --git a/app/js/controllers.js b/app/js/controllers.js index 8fe3cfe0..a3a1b7af 100644 --- a/app/js/controllers.js +++ b/app/js/controllers.js @@ -497,7 +497,11 @@ angular.module('myApp.controllers', []) return all; }); - AppMessagesManager.sendText($scope.curDialog.peerID, text); + do { + AppMessagesManager.sendText($scope.curDialog.peerID, text.substr(0, 4096)); + text = text.substr(4096); + } while (text.length); + resetDraft(); $scope.$broadcast('ui_message_send'); }); diff --git a/app/js/directives.js b/app/js/directives.js index 3aa96255..e35fee9b 100644 --- a/app/js/directives.js +++ b/app/js/directives.js @@ -16,7 +16,7 @@ angular.module('myApp.directives', ['myApp.filters']) restrict: 'AE', scope: true, translude: false, - templateUrl: 'partials/dialog.html?4' + templateUrl: 'partials/dialog.html?5' }; }) @@ -25,7 +25,7 @@ angular.module('myApp.directives', ['myApp.filters']) restrict: 'AE', scope: true, translude: false, - templateUrl: 'partials/message.html?6' + templateUrl: 'partials/message.html?7' }; }) @@ -340,7 +340,7 @@ angular.module('myApp.directives', ['myApp.filters']) var lastTyping = 0; $(editorElement).on('keyup', function (e) { - var now = +new Date(); + var now = tsNow(); if (now - lastTyping < 5000) { return; } @@ -690,7 +690,7 @@ angular.module('myApp.directives', ['myApp.filters']) function link (scope, element, attrs) { var promise = $interval(function () { - var time = +new Date(), + var time = tsNow(), cnt = 3; if (time % 1000 <= 200) { @@ -708,3 +708,24 @@ angular.module('myApp.directives', ['myApp.filters']) }); } }) + + .directive('myAudioAutoplay', function() { + + return { + link: link, + scope: { + audio: '=' + } + }; + + function link (scope, element, attrs) { + scope.$watch('audio.autoplay', function (autoplay) { + if (autoplay) { + element.autoplay = true; + element[0].play(); + } else { + element.autoplay = false; + } + }); + } + }) diff --git a/app/js/filters.js b/app/js/filters.js index 37611f01..cc8c3493 100644 --- a/app/js/filters.js +++ b/app/js/filters.js @@ -61,7 +61,7 @@ angular.module('myApp.filters', []) } var ticks = timestamp * 1000, - diff = Math.abs(+new Date() - ticks), + diff = Math.abs(tsNow() - ticks), format = 'HH:mm'; if (diff > 518400000) { // 6 days @@ -142,7 +142,7 @@ angular.module('myApp.filters', []) .filter('relativeTime', ['$filter', function($filter) { return function (timestamp) { var ticks = timestamp * 1000, - diff = Math.abs(+new Date() - ticks); + diff = Math.abs(tsNow() - ticks); if (diff < 60000) { return 'just now'; diff --git a/app/js/lib/mtproto.js b/app/js/lib/mtproto.js index 82c7df22..696d45c8 100644 --- a/app/js/lib/mtproto.js +++ b/app/js/lib/mtproto.js @@ -1011,7 +1011,7 @@ factory('MtpMessageIdGenerator', function (AppConfigManager) { }); function generateMessageID () { - var timeTicks = 1 * (new Date()), + var timeTicks = tsNow(), timeSec = Math.floor(timeTicks / 1000) + timeOffset, timeMSec = timeTicks % 1000, random = nextRandomInt(0xFFFF); @@ -1031,7 +1031,7 @@ factory('MtpMessageIdGenerator', function (AppConfigManager) { }; function applyServerTime (serverTime, localTime) { - var newTimeOffset = serverTime - Math.floor((localTime || +new Date()) / 1000), + var newTimeOffset = serverTime - Math.floor((localTime || tsNow()) / 1000), changed = Math.abs(timeOffset - newTimeOffset) > 10; AppConfigManager.set({server_time_offset: newTimeOffset}); @@ -1224,7 +1224,7 @@ factory('MtpAuthorizer', function (MtpDcConfigurator, MtpRsaKeysManager, MtpSecu }; function mtpDecryptServerDhDataAnswer (auth, encryptedAnswer) { - auth.localTime = +new Date(); + auth.localTime = tsNow(); auth.tmpAesKey = sha1Hash(auth.newNonce.concat(auth.serverNonce)).concat(sha1Hash(auth.serverNonce.concat(auth.newNonce)).slice(0, 12)); auth.tmpAesIv = sha1Hash(auth.serverNonce.concat(auth.newNonce)).slice(12).concat(sha1Hash([].concat(auth.newNonce, auth.newNonce)), auth.newNonce.slice(0, 4)); @@ -1667,8 +1667,8 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato MtpNetworker.prototype.checkLongPoll = function(force) { var isClean = this.cleanupSent(); - // console.log('Check lp', this.longPollPending, (new Date().getTime())); - if (this.longPollPending && (new Date().getTime()) < this.longPollPending) { + // console.log('Check lp', this.longPollPending, tsNow()); + if (this.longPollPending && tsNow() < this.longPollPending) { return false; } var self = this; @@ -1683,8 +1683,8 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato MtpNetworker.prototype.sendLongPoll = function() { var maxWait = 25000; - this.longPollPending = (new Date().getTime()) + maxWait; - // console.log('Set lp', this.longPollPending, (new Date().getTime())); + this.longPollPending = tsNow() + maxWait; + // console.log('Set lp', this.longPollPending, tsNow()); this.wrapMtpCall('http_wait', {max_delay: 0, wait_after: 0, max_wait: maxWait}, {noResponse: true}). then((function () { @@ -1707,7 +1707,7 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato }; MtpNetworker.prototype.pushResend = function(messageID, delay) { - var value = delay ? (new Date()).getTime() + delay : 0; + var value = delay ? tsNow() + delay : 0; var sentMessage = this.sentMessages[messageID]; if (sentMessage.container) { for (var i = 0; i < sentMessage.inner.length; i++) { @@ -1766,7 +1766,7 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato var messages = [], message, messagesByteLen = 0, - currentTime = (new Date()).getTime(), + currentTime = tsNow(), self = this; angular.forEach(this.pendingMessages, function (value, messageID) { @@ -1970,7 +1970,7 @@ factory('MtpNetworkerFactory', function (MtpDcConfigurator, MtpMessageIdGenerato }; MtpNetworker.prototype.sheduleRequest = function (delay) { - var nextReq = new Date() + delay; + var nextReq = tsNow() + delay; if (delay && this.nextReq && this.nextReq <= nextReq) { return false; @@ -2465,6 +2465,9 @@ factory('MtpApiFileManager', function (MtpApiManager, $q, $window) { case 'inputDocumentFileLocation': return 'doc' + location.id; + + case 'inputAudioFileLocation': + return 'audio' + location.id; } return location.volume_id + '_' + location.local_id + '_' + location.secret + '.jpg'; }; diff --git a/app/js/services.js b/app/js/services.js index 2b18d354..f431a954 100644 --- a/app/js/services.js +++ b/app/js/services.js @@ -503,7 +503,7 @@ angular.module('myApp.services', []) } }) -.service('AppMessagesManager', function ($q, $rootScope, $filter, $sanitize, $location, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, SearchIndexManager) { +.service('AppMessagesManager', function ($q, $rootScope, $filter, $sanitize, $location, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, AppAudioManager, MtpApiManager, MtpApiFileManager, RichTextProcessor, NotificationsManager, SearchIndexManager) { var messagesStorage = {}; var messagesForHistory = {}; @@ -776,6 +776,9 @@ angular.module('myApp.services', []) if (apiMessage.media && apiMessage.media._ == 'messageMediaDocument') { AppDocsManager.saveDoc(apiMessage.media.document); } + if (apiMessage.media && apiMessage.media._ == 'messageMediaAudio') { + AppAudioManager.saveAudio(apiMessage.media.audio); + } if (apiMessage.action && apiMessage.action._ == 'messageActionChatEditPhoto') { AppPhotosManager.savePhoto(apiMessage.action.photo); } @@ -802,14 +805,30 @@ angular.module('myApp.services', []) to_id: AppPeersManager.getOutputPeer(peerID), out: true, unread: true, - date: (+new Date()) / 1000, + date: tsNow() / 1000, message: text, media: {_: 'messageMediaEmpty'}, random_id: randomIDS, pending: true }; + var toggleError = function (on) { + var historyMessage = messagesForHistory[messageID]; + if (on) { + message.error = true; + if (historyMessage) { + historyMessage.error = true; + } + } else { + delete message.error; + if (historyMessage) { + delete historyMessage.error; + } + } + } + message.send = function () { + toggleError(false); MtpApiManager.invokeApi('messages.sendMessage', { peer: inputPeer, message: text, @@ -830,6 +849,8 @@ angular.module('myApp.services', []) pts: result.pts }); } + }, function (error) { + toggleError(true); }); }; @@ -860,10 +881,14 @@ angular.module('myApp.services', []) attachType = 'photo'; } else if (file.type.substr(0, 6) == 'video/') { attachType = 'video'; + } else if (file.type == 'audio/mpeg' || file.type == 'audio/mp3') { + attachType = 'audio'; } else { attachType = 'doc'; } + console.log(11, options.isMedia, file.type, attachType); + if (historyStorage === undefined) { historyStorage = historiesStorage[peerID] = {count: null, history: [], pending: []}; } @@ -884,7 +909,7 @@ angular.module('myApp.services', []) to_id: AppPeersManager.getOutputPeer(peerID), out: true, unread: true, - date: (+new Date()) / 1000, + date: tsNow() / 1000, message: '', media: media, random_id: randomIDS, @@ -903,6 +928,10 @@ angular.module('myApp.services', []) inputMedia = {_: 'inputMediaUploadedVideo', file: inputFile, duration: 0, w: 0, h: 0}; break; + case 'audio': + inputMedia = {_: 'inputMediaUploadedAudio', file: inputFile, duration: 0}; + break; + case 'doc': default: inputMedia = {_: 'inputMediaUploadedDocument', file: inputFile, file_name: file.name, mime_type: file.type}; @@ -930,8 +959,12 @@ angular.module('myApp.services', []) }); } + }, function (error) { + toggleError(true); }); - }, null, function (progress) { + }, function (error) { + toggleError(true); + }, function (progress) { // console.log('upload progress', progress); var historyMessage = messagesForHistory[messageID], percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); @@ -950,9 +983,7 @@ angular.module('myApp.services', []) historyStorage.pending.unshift(messageID); $rootScope.$broadcast('history_append', {peerID: peerID, messageID: messageID, my: true}); - // setTimeout(function () { - message.send(); - // }, 5000); + message.send(); }); pendingByRandomID[randomIDS] = [peerID, messageID]; @@ -982,6 +1013,7 @@ angular.module('myApp.services', []) if (message = messagesStorage[tempID]) { delete message.pending; + delete message.error; delete message.random_id; delete message.send; } @@ -989,6 +1021,7 @@ angular.module('myApp.services', []) if (historyMessage = messagesForHistory[tempID]) { messagesForHistory[finalMessage.id] = angular.extend(historyMessage, wrapForHistory(finalMessage.id)); delete historyMessage.pending; + delete historyMessage.error; delete historyMessage.random_id; delete historyMessage.send; } @@ -1070,6 +1103,10 @@ angular.module('myApp.services', []) case 'messageMediaDocument': message.media.document = AppDocsManager.wrapForHistory(message.media.document.id); break; + + case 'messageMediaAudio': + message.media.audio = AppAudioManager.wrapForHistory(message.media.audio.id); + break; } if (message.media.user_id) { @@ -1510,7 +1547,7 @@ angular.module('myApp.services', []) } }) -.service('AppDocsManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager, AppUsersManager) { +.service('AppDocsManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager) { var docs = {}; var docsForHistory = {}; @@ -1624,6 +1661,66 @@ angular.module('myApp.services', []) } }) +.service('AppAudioManager', function ($rootScope, $modal, $window, $timeout, $sce, MtpApiFileManager) { + var audios = {}; + var audiosForHistory = {}; + + function saveAudio (apiAudio) { + audios[apiAudio.id] = apiAudio; + }; + + function wrapForHistory (audioID) { + if (audiosForHistory[audioID] !== undefined) { + return audiosForHistory[audioID]; + } + + var audio = angular.copy(audios[audioID]); + + return audiosForHistory[audioID] = audio; + } + + function openAudio (audioID, accessHash) { + var audio = audios[audioID], + historyAudio = audiosForHistory[audioID] || audio || {}, + inputFileLocation = { + _: 'inputAudioFileLocation', + id: audioID, + access_hash: accessHash || audio.access_hash + }; + + historyAudio.progress = {enabled: true, percent: 1, total: audio.size}; + + function updateDownloadProgress (progress) { + console.log('dl progress', progress); + historyAudio.progress.done = progress.done; + historyAudio.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); + $rootScope.$broadcast('history_update'); + } + + MtpApiFileManager.downloadFile(audio.dc_id, inputFileLocation, audio.size).then(function (url) { + delete historyAudio.progress; + historyAudio.url = $sce.trustAsResourceUrl(url); + historyAudio.autoplay = true; + $timeout(function () { + console.log('disable autoplay'); + delete historyAudio.autoplay; + $rootScope.$broadcast('history_update'); + }, 1000); + }, function (e) { + console.log('document download failed', e); + historyDoc.progress.enabled = false; + }, updateDownloadProgress); + } + + $rootScope.openAudio = openAudio; + + return { + saveAudio: saveAudio, + wrapForHistory: wrapForHistory, + openAudio: openAudio + } +}) + .service('ExternalResourcesManager', function ($q, $http) { var urlPromises = {}; @@ -2042,7 +2139,7 @@ angular.module('myApp.services', []) } function sendUpdateStatusReq(offline) { - var date = (1 * new Date()); + var date = tsNow(); if (offline && !lastOnlineUpdated || !offline && (date - lastOnlineUpdated) < 50000) { return; @@ -2094,7 +2191,7 @@ angular.module('myApp.services', []) titleBackup = document.title; titlePromise = $interval(function () { - var time = +new Date(); + var time = tsNow(); if (!notificationsCount || time % 2000 > 1000) { document.title = titleBackup; $('link[rel="icon"]').replaceWith(faviconBackupEl); @@ -2114,7 +2211,8 @@ angular.module('myApp.services', []) notify: notify, cancel: notificationCancel, clear: notificationsClear, - getPeerSettings: getPeerSettings + getPeerSettings: getPeerSettings, + getPeerMuted: getPeerMuted }; function getPeerSettings (peerID) { @@ -2127,10 +2225,13 @@ angular.module('myApp.services', []) _: 'inputNotifyPeer', peer: AppPeersManager.getInputPeerByID(peerID) } - }).then(function (peerNotifySettings) { - // console.log('got settings', peerID, peerNotifySettings); + }); + } + + function getPeerMuted (peerID) { + return getPeerSettings(peerID).then(function (peerNotifySettings) { return peerNotifySettings._ == 'peerNotifySettings' && - peerNotifySettings.mute_until * 1000 > (+new Date()); + peerNotifySettings.mute_until * 1000 > tsNow(); }); } @@ -2196,7 +2297,7 @@ angular.module('myApp.services', []) notification.onclose = function () { delete notificationsShown[key]; - // lastClosed.push(+new Date()); + // lastClosed.push(tsNow()); notificationsClear(); }; diff --git a/app/js/util.js b/app/js/util.js index e65c18d5..3d2d0b5a 100644 --- a/app/js/util.js +++ b/app/js/util.js @@ -52,4 +52,8 @@ function onCtrlEnter (textarea, cb) { function onContentLoaded (cb) { setTimeout(cb, 0); -}; \ No newline at end of file +}; + +function tsNow () { + return +new Date(); +} diff --git a/app/partials/dialog.html b/app/partials/dialog.html index 1a3c4dd2..1ded7010 100644 --- a/app/partials/dialog.html +++ b/app/partials/dialog.html @@ -9,6 +9,10 @@ ng-show="dialogMessage.unreadCount > 0" ng-bind="dialogMessage.unreadCount" > +
@@ -41,36 +45,19 @@ - - Photo - - - Video - - - Document - - - Location - - - Contact - + Photo + Video + Document + Audio + Location + Contact - - created the group - - - changed group name - - - changed group photo - - - removed group photo - + created the group + changed group name + changed group photo + removed group photo diff --git a/app/partials/im.html b/app/partials/im.html index 8d7fb21a..d7afad27 100644 --- a/app/partials/im.html +++ b/app/partials/im.html @@ -120,7 +120,7 @@
- +
diff --git a/app/partials/message.html b/app/partials/message.html index d72f71f9..e021c8f2 100644 --- a/app/partials/message.html +++ b/app/partials/message.html @@ -55,7 +55,10 @@
- + + + +
+ +
+ + + + + +
+
+ + Voice message + + + {{historyMessage.media.audio.duration | duration}} + + + {{historyMessage.media.audio.progress | formatSizeProgress}} + +
+
+ Play +
+
+
+
+ + {{historyMessage.media.audio.progress.percent}}% Complete (success) + +
+
+
+
+ +
+
+
+ @@ -158,7 +201,7 @@
- +
diff --git a/app/vendor/ui-bootstrap/ui-bootstrap-custom-0.10.0.js b/app/vendor/ui-bootstrap/ui-bootstrap-custom-0.10.0.js new file mode 100644 index 00000000..a3386d7a --- /dev/null +++ b/app/vendor/ui-bootstrap/ui-bootstrap-custom-0.10.0.js @@ -0,0 +1,990 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.10.0 - 2014-01-28 + * License: MIT + */ +angular.module("ui.bootstrap", ["ui.bootstrap.dropdownToggle","ui.bootstrap.transition","ui.bootstrap.modal","ui.bootstrap.position","ui.bootstrap.bindHtml","ui.bootstrap.tooltip"]); +/* + * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js + * @restrict class or attribute + * @example: + + */ + +angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) { + var openElement = null, + closeMenu = angular.noop; + return { + restrict: 'CA', + link: function(scope, element, attrs) { + scope.$watch('$location.path', function() { closeMenu(); }); + element.parent().bind('click', function() { closeMenu(); }); + element.bind('click', function (event) { + + var elementWasOpen = (element === openElement); + + event.preventDefault(); + event.stopPropagation(); + + if (!!openElement) { + closeMenu(); + } + + if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) { + element.parent().addClass('open'); + openElement = element; + closeMenu = function (event) { + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + $document.unbind('click', closeMenu); + element.parent().removeClass('open'); + closeMenu = angular.noop; + openElement = null; + }; + $document.bind('click', closeMenu); + } + }); + } + }; +}]); + +angular.module('ui.bootstrap.transition', []) + +/** + * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. + * @param {DOMElement} element The DOMElement that will be animated. + * @param {string|object|function} trigger The thing that will cause the transition to start: + * - As a string, it represents the css class to be added to the element. + * - As an object, it represents a hash of style attributes to be applied to the element. + * - As a function, it represents a function to be called that will cause the transition to occur. + * @return {Promise} A promise that is resolved when the transition finishes. + */ +.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { + + var $transition = function(element, trigger, options) { + options = options || {}; + var deferred = $q.defer(); + var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"]; + + var transitionEndHandler = function(event) { + $rootScope.$apply(function() { + element.unbind(endEventName, transitionEndHandler); + deferred.resolve(element); + }); + }; + + if (endEventName) { + element.bind(endEventName, transitionEndHandler); + } + + // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur + $timeout(function() { + if ( angular.isString(trigger) ) { + element.addClass(trigger); + } else if ( angular.isFunction(trigger) ) { + trigger(element); + } else if ( angular.isObject(trigger) ) { + element.css(trigger); + } + //If browser does not support transitions, instantly resolve + if ( !endEventName ) { + deferred.resolve(element); + } + }); + + // Add our custom cancel function to the promise that is returned + // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, + // i.e. it will therefore never raise a transitionEnd event for that transition + deferred.promise.cancel = function() { + if ( endEventName ) { + element.unbind(endEventName, transitionEndHandler); + } + deferred.reject('Transition cancelled'); + }; + + return deferred.promise; + }; + + // Work out the name of the transitionEnd event + var transElement = document.createElement('trans'); + var transitionEndEventNames = { + 'WebkitTransition': 'webkitTransitionEnd', + 'MozTransition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'transition': 'transitionend' + }; + var animationEndEventNames = { + 'WebkitTransition': 'webkitAnimationEnd', + 'MozTransition': 'animationend', + 'OTransition': 'oAnimationEnd', + 'transition': 'animationend' + }; + function findEndEventName(endEventNames) { + for (var name in endEventNames){ + if (transElement.style[name] !== undefined) { + return endEventNames[name]; + } + } + } + $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); + $transition.animationEndEventName = findEndEventName(animationEndEventNames); + return $transition; +}]); + +angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) + +/** + * A helper, internal data structure that acts as a map but also allows getting / removing + * elements in the LIFO order + */ + .factory('$$stackedMap', function () { + return { + createNew: function () { + var stack = []; + + return { + add: function (key, value) { + stack.push({ + key: key, + value: value + }); + }, + get: function (key) { + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + return stack[i]; + } + } + }, + keys: function() { + var keys = []; + for (var i = 0; i < stack.length; i++) { + keys.push(stack[i].key); + } + return keys; + }, + top: function () { + return stack[stack.length - 1]; + }, + remove: function (key) { + var idx = -1; + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + idx = i; + break; + } + } + return stack.splice(idx, 1)[0]; + }, + removeTop: function () { + return stack.splice(stack.length - 1, 1)[0]; + }, + length: function () { + return stack.length; + } + }; + } + }; + }) + +/** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', ['$timeout', function ($timeout) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/modal/backdrop.html', + link: function (scope) { + + scope.animate = false; + + //trigger CSS transitions + $timeout(function () { + scope.animate = true; + }); + } + }; + }]) + + .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { + return { + restrict: 'EA', + scope: { + index: '@', + animate: '=' + }, + replace: true, + transclude: true, + templateUrl: 'template/modal/window.html', + link: function (scope, element, attrs) { + scope.windowClass = attrs.windowClass || ''; + + $timeout(function () { + // trigger CSS transitions + scope.animate = true; + // focus a freshly-opened modal + element[0].focus(); + }); + + scope.close = function (evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + } + }; + }]) + + .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', + function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { + + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropDomEl, backdropScope; + var openedWindows = $$stackedMap.createNew(); + var $modalStack = {}; + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex){ + if (backdropScope) { + backdropScope.index = newBackdropIndex; + } + }); + + function removeModalWindow(modalInstance) { + + var body = $document.find('body').eq(0); + var modalWindow = openedWindows.get(modalInstance).value; + + //clean up the stack + openedWindows.remove(modalInstance); + + //remove window DOM element + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, checkRemoveBackdrop); + body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); + } + + function checkRemoveBackdrop() { + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { + backdropScopeRef.$destroy(); + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } + } + + function removeAfterAnimate(domEl, scope, emulateTime, done) { + // Closing animation + scope.animate = false; + + var transitionEndEventName = $transition.transitionEndEventName; + if (transitionEndEventName) { + // transition out + var timeout = $timeout(afterAnimating, emulateTime); + + domEl.bind(transitionEndEventName, function () { + $timeout.cancel(timeout); + afterAnimating(); + scope.$apply(); + }); + } else { + // Ensure this call is async + $timeout(afterAnimating, 0); + } + + function afterAnimating() { + if (afterAnimating.done) { + return; + } + afterAnimating.done = true; + + domEl.remove(); + if (done) { + done(); + } + } + } + + $document.bind('keydown', function (evt) { + var modal; + + if (evt.which === 27) { + modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + $rootScope.$apply(function () { + $modalStack.dismiss(modal.key); + }); + } + } + }); + + $modalStack.open = function (modalInstance, modal) { + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard + }); + + var body = $document.find('body').eq(0), + currBackdropIndex = backdropIndex(); + + if (currBackdropIndex >= 0 && !backdropDomEl) { + backdropScope = $rootScope.$new(true); + backdropScope.index = currBackdropIndex; + backdropDomEl = $compile('
')(backdropScope); + body.append(backdropDomEl); + } + + var angularDomEl = angular.element('
'); + angularDomEl.attr('window-class', modal.windowClass); + angularDomEl.attr('index', openedWindows.length() - 1); + angularDomEl.attr('animate', 'animate'); + angularDomEl.html(modal.content); + + var modalDomEl = $compile(angularDomEl)(modal.scope); + openedWindows.top().value.modalDomEl = modalDomEl; + body.append(modalDomEl); + body.addClass(OPENED_MODAL_CLASS); + }; + + $modalStack.close = function (modalInstance, result) { + var modalWindow = openedWindows.get(modalInstance).value; + if (modalWindow) { + modalWindow.deferred.resolve(result); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismiss = function (modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance).value; + if (modalWindow) { + modalWindow.deferred.reject(reason); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismissAll = function (reason) { + var topModal = this.getTop(); + while (topModal) { + this.dismiss(topModal.key, reason); + topModal = this.getTop(); + } + }; + + $modalStack.getTop = function () { + return openedWindows.top(); + }; + + return $modalStack; + }]) + + .provider('$modal', function () { + + var $modalProvider = { + options: { + backdrop: true, //can be also false or 'static' + keyboard: true + }, + $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', + function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { + + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) { + return result.data; + }); + } + + function getResolvePromises(resolves) { + var promisesArr = []; + angular.forEach(resolves, function (value, key) { + if (angular.isFunction(value) || angular.isArray(value)) { + promisesArr.push($q.when($injector.invoke(value))); + } + }); + return promisesArr; + } + + $modal.open = function (modalOptions) { + + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + close: function (result) { + $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + $modalStack.dismiss(modalInstance, reason); + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + + + templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { + + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$modalInstance = modalInstance; + angular.forEach(modalOptions.resolve, function (value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + content: tplAndVars[0], + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + windowClass: modalOptions.windowClass + }); + + }, function resolveError(reason) { + modalResultDeferred.reject(reason); + }); + + templateAndResolvePromise.then(function () { + modalOpenedDeferred.resolve(true); + }, function () { + modalOpenedDeferred.reject(false); + }); + + return modalInstance; + }; + + return $modal; + }] + }; + + return $modalProvider; + }); + +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', ['$document', '$window', function ($document, $window) { + + function getStyle(el, cssprop) { + if (el.currentStyle) { //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, "position") || 'static' ) === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + var parentOffsetEl = function (element) { + var docDomEl = $document[0]; + var offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function (element) { + var elBCR = this.offset(element); + var offsetParentBCR = { top: 0, left: 0 }; + var offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft) + }; + } + }; + }]); + +angular.module('ui.bootstrap.bindHtml', []) + + .directive('bindHtmlUnsafe', function () { + return function (scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); + scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; + }); +/** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ +angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider( '$tooltip', function () { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + animation: true, + popupDelay: 0 + }; + + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'focus': 'blur' + }; + + // The options specified to the provider globally. + var globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function( value ) { + angular.extend( globalOptions, value ); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + */ + this.setTriggers = function setTriggers ( triggers ) { + angular.extend( triggerMap, triggers ); + }; + + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name){ + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) { + return function $tooltip ( type, prefix, defaultTriggerShow ) { + var options = angular.extend( {}, defaultOptions, globalOptions ); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers ( trigger ) { + var show = trigger || options.trigger || defaultTriggerShow; + var hide = triggerMap[show] || show; + return { + show: show, + hide: hide + }; + } + + var directiveName = snake_case( type ); + + var startSym = $interpolate.startSymbol(); + var endSym = $interpolate.endSymbol(); + var template = + '
'+ + '
'; + + return { + restrict: 'EA', + scope: true, + compile: function (tElem, tAttrs) { + var tooltipLinker = $compile( template ); + + return function link ( scope, element, attrs ) { + var tooltip; + var transitionTimeout; + var popupTimeout; + var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; + var triggers = getTriggers( undefined ); + var hasRegisteredTriggers = false; + var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + + var positionTooltip = function (){ + var position, + ttWidth, + ttHeight, + ttPosition; + // Get the position of the directive element. + position = appendToBody ? $position.offset( element ) : $position.position( element ); + + // Get the height and width of the tooltip so we can center it. + ttWidth = tooltip.prop( 'offsetWidth' ); + ttHeight = tooltip.prop( 'offsetHeight' ); + + // Calculate the tooltip's top and left coordinates to center it with + // this directive. + switch ( scope.tt_placement ) { + case 'right': + ttPosition = { + top: position.top + position.height / 2 - ttHeight / 2, + left: position.left + position.width + }; + break; + case 'bottom': + ttPosition = { + top: position.top + position.height, + left: position.left + position.width / 2 - ttWidth / 2 + }; + break; + case 'left': + ttPosition = { + top: position.top + position.height / 2 - ttHeight / 2, + left: position.left - ttWidth + }; + break; + default: + ttPosition = { + top: position.top - ttHeight, + left: position.left + position.width / 2 - ttWidth / 2 + }; + break; + } + + ttPosition.top += 'px'; + ttPosition.left += 'px'; + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + + }; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + scope.tt_isOpen = false; + + function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + return; + } + if ( scope.tt_popupDelay ) { + popupTimeout = $timeout( show, scope.tt_popupDelay, false ); + popupTimeout.then(function(reposition){reposition();}); + } else { + show()(); + } + } + + function hideTooltipBind () { + scope.$apply(function () { + hide(); + }); + } + + // Show the tooltip popup element. + function show() { + + + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return angular.noop; + } + + createTooltip(); + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + } + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + if ( appendToBody ) { + $document.find( 'body' ).append( tooltip ); + } else { + element.after( tooltip ); + } + + positionTooltip(); + + // And show the tooltip. + scope.tt_isOpen = true; + scope.$digest(); // digest required as $apply is not called + + // Return positioning function as promise callback for correct + // positioning after draw. + return positionTooltip; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + scope.tt_isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel( popupTimeout ); + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( scope.tt_animation ) { + transitionTimeout = $timeout(removeTooltip, 500); + } else { + removeTooltip(); + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + removeTooltip(); + } + tooltip = tooltipLinker(scope, function () {}); + + // Get contents rendered into the tooltip + scope.$digest(); + } + + function removeTooltip() { + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + } + + /** + * Observe the relevant attributes. + */ + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + + if (!val && scope.tt_isOpen ) { + hide(); + } + }); + + attrs.$observe( prefix+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( prefix+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( prefix+'PopupDelay', function ( val ) { + var delay = parseInt( val, 10 ); + scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + }); + + var unregisterTriggers = function() { + if (hasRegisteredTriggers) { + element.unbind( triggers.show, showTooltipBind ); + element.unbind( triggers.hide, hideTooltipBind ); + } + }; + + attrs.$observe( prefix+'Trigger', function ( val ) { + unregisterTriggers(); + + triggers = getTriggers( val ); + + if ( triggers.show === triggers.hide ) { + element.bind( triggers.show, toggleTooltipBind ); + } else { + element.bind( triggers.show, showTooltipBind ); + element.bind( triggers.hide, hideTooltipBind ); + } + + hasRegisteredTriggers = true; + }); + + var animation = scope.$eval(attrs[prefix + 'Animation']); + scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; + + attrs.$observe( prefix+'AppendToBody', function ( val ) { + appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; + }); + + // if a tooltip is attached to we need to remove it on + // location change as its parent scope will probably not be destroyed + // by the change. + if ( appendToBody ) { + scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { + if ( scope.tt_isOpen ) { + hide(); + } + }); + } + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + $timeout.cancel( transitionTimeout ); + $timeout.cancel( popupTimeout ); + unregisterTriggers(); + removeTooltip(); + }); + }; + } + }; + }; + }]; +}) + +.directive( 'tooltipPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' + }; +}) + +.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); +}]) + +.directive( 'tooltipHtmlUnsafePopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + }; +}) + +.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); +}]); diff --git a/app/vendor/ui-bootstrap/ui-bootstrap-custom-0.10.0.min.js b/app/vendor/ui-bootstrap/ui-bootstrap-custom-0.10.0.min.js new file mode 100644 index 00000000..ea7f5455 --- /dev/null +++ b/app/vendor/ui-bootstrap/ui-bootstrap-custom-0.10.0.min.js @@ -0,0 +1,8 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.10.0 - 2014-01-28 + * License: MIT + */ +angular.module("ui.bootstrap",["ui.bootstrap.dropdownToggle","ui.bootstrap.transition","ui.bootstrap.modal","ui.bootstrap.position","ui.bootstrap.bindHtml","ui.bootstrap.tooltip"]),angular.module("ui.bootstrap.dropdownToggle",[]).directive("dropdownToggle",["$document","$location",function(a){var b=null,c=angular.noop;return{restrict:"CA",link:function(d,e){d.$watch("$location.path",function(){c()}),e.parent().bind("click",function(){c()}),e.bind("click",function(d){var f=e===b;d.preventDefault(),d.stopPropagation(),b&&c(),f||e.hasClass("disabled")||e.prop("disabled")||(e.parent().addClass("open"),b=e,c=function(d){d&&(d.preventDefault(),d.stopPropagation()),a.unbind("click",c),e.parent().removeClass("open"),c=angular.noop,b=null},a.bind("click",c))})}}}]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0)}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g,0)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&e.$apply(function(){o.dismiss(b.key)}))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();h>=0&&!k&&(l=e.$new(!0),l.index=h,k=d("
")(l),f.append(k));var i=angular.element("
");i.attr("window-class",b.windowClass),i.attr("index",n.length()-1),i.attr("animate","animate"),i.html(b.content);var j=d(i)(b.scope);n.top().value.modalDomEl=j,f.append(j),f.addClass(m)},o.close=function(a,b){var c=n.get(a).value;c&&(c.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a).value;c&&(c.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].body.scrollTop||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].body.scrollLeft||a[0].documentElement.scrollLeft)}}}}]),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
"+"
";return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!z||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return b.tt_content?(r(),u&&g.cancel(u),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),A(),b.tt_isOpen=!0,b.$digest(),A):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),b.tt_animation?u=g(s,500):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=!1,z=angular.isDefined(d[l+"Enable"]),A=function(){var a,d,e,f;switch(a=w?j.offset(c):j.position(c),d=t.prop("offsetWidth"),e=t.prop("offsetHeight"),b.tt_placement){case"right":f={top:a.top+a.height/2-e/2,left:a.left+a.width};break;case"bottom":f={top:a.top+a.height,left:a.left+a.width/2-d/2};break;case"left":f={top:a.top+a.height/2-e/2,left:a.left-d};break;default:f={top:a.top-e,left:a.left+a.width/2-d/2}}f.top+="px",f.left+="px",t.css(f)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)?a:o.placement}),d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var B=function(){y&&(c.unbind(x.show,k),c.unbind(x.hide,m))};d.$observe(l+"Trigger",function(a){B(),x=n(a),x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m)),y=!0});var C=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(C)?!!C:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),B(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]); \ No newline at end of file diff --git a/app/vendor/ui-bootstrap/ui-bootstrap-custom-0.7.0.js b/app/vendor/ui-bootstrap/ui-bootstrap-custom-0.7.0.js deleted file mode 100644 index 731c18e7..00000000 --- a/app/vendor/ui-bootstrap/ui-bootstrap-custom-0.7.0.js +++ /dev/null @@ -1,332 +0,0 @@ -angular.module("ui.bootstrap", ["ui.bootstrap.modal"]); -angular.module('ui.bootstrap.modal', []) - -/** - * A helper, internal data structure that acts as a map but also allows getting / removing - * elements in the LIFO order - */ - .factory('$$stackedMap', function () { - return { - createNew: function () { - var stack = []; - - return { - add: function (key, value) { - stack.push({ - key: key, - value: value - }); - }, - get: function (key) { - for (var i = 0; i < stack.length; i++) { - if (key == stack[i].key) { - return stack[i]; - } - } - }, - keys: function() { - var keys = []; - for (var i = 0; i < stack.length; i++) { - keys.push(stack[i].key); - } - return keys; - }, - top: function () { - return stack[stack.length - 1]; - }, - remove: function (key) { - var idx = -1; - for (var i = 0; i < stack.length; i++) { - if (key == stack[i].key) { - idx = i; - break; - } - } - return stack.splice(idx, 1)[0]; - }, - removeTop: function () { - return stack.splice(stack.length - 1, 1)[0]; - }, - length: function () { - return stack.length; - } - }; - } - }; - }) - -/** - * A helper directive for the $modal service. It creates a backdrop element. - */ - .directive('modalBackdrop', ['$modalStack', '$timeout', function ($modalStack, $timeout) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/modal/backdrop.html', - link: function (scope, element, attrs) { - - //trigger CSS transitions - $timeout(function () { - scope.animate = true; - }); - - scope.close = function (evt) { - var modal = $modalStack.getTop(); - if (modal && modal.value.backdrop && modal.value.backdrop != 'static') { - evt.preventDefault(); - evt.stopPropagation(); - $modalStack.dismiss(modal.key, 'backdrop click'); - } - }; - } - }; - }]) - - .directive('modalWindow', ['$timeout', function ($timeout) { - return { - restrict: 'EA', - scope: { - index: '@' - }, - replace: true, - transclude: true, - templateUrl: 'template/modal/window.html', - link: function (scope, element, attrs) { - scope.windowClass = attrs.windowClass || ''; - - //trigger CSS transitions - $timeout(function () { - scope.animate = true; - }); - - scope.close = function (evt) { - var modal = $modalStack.getTop(); - if (modal) { - $modalStack.dismiss(modal.key, 'close click'); - } - }; - } - }; - }]) - - .factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap', - function ($document, $compile, $rootScope, $$stackedMap) { - - var backdropjqLiteEl, backdropDomEl; - var backdropScope = $rootScope.$new(true); - var body = $document.find('body').eq(0); - var openedWindows = $$stackedMap.createNew(); - var $modalStack = {}; - - function backdropIndex() { - var topBackdropIndex = -1; - var opened = openedWindows.keys(); - for (var i = 0; i < opened.length; i++) { - if (openedWindows.get(opened[i]).value.backdrop) { - topBackdropIndex = i; - } - } - return topBackdropIndex; - } - - $rootScope.$watch(backdropIndex, function(newBackdropIndex){ - backdropScope.index = newBackdropIndex; - }); - - function removeModalWindow(modalInstance) { - - var modalWindow = openedWindows.get(modalInstance).value; - - //clean up the stack - openedWindows.remove(modalInstance); - - //remove window DOM element - modalWindow.modalDomEl.remove(); - - //remove backdrop if no longer needed - if (backdropDomEl && backdropIndex() == -1) { - backdropDomEl.remove(); - backdropDomEl = undefined; - } - - //destroy scope - modalWindow.modalScope.$destroy(); - } - - $document.bind('keydown', function (evt) { - var modal; - - if (evt.which === 27) { - modal = openedWindows.top(); - if (modal && modal.value.keyboard) { - $rootScope.$apply(function () { - $modalStack.dismiss(modal.key); - }); - } - } - }); - - $modalStack.open = function (modalInstance, modal) { - openedWindows.add(modalInstance, { - deferred: modal.deferred, - modalScope: modal.scope, - backdrop: modal.backdrop, - keyboard: modal.keyboard - }); - - var angularDomEl = angular.element('
'); - angularDomEl.attr('window-class', modal.windowClass); - angularDomEl.attr('index', openedWindows.length() - 1); - angularDomEl.html(modal.content); - - var modalDomEl = $compile(angularDomEl)(modal.scope); - openedWindows.top().value.modalDomEl = modalDomEl; - body.append(modalDomEl); - - if (backdropIndex() >= 0 && !backdropDomEl) { - backdropjqLiteEl = angular.element('
'); - backdropDomEl = $compile(backdropjqLiteEl)(backdropScope); - body.append(backdropDomEl); - } - }; - - $modalStack.close = function (modalInstance, result) { - var modal = openedWindows.get(modalInstance); - if (modal) { - modal.value.deferred.resolve(result); - removeModalWindow(modalInstance); - } - }; - - $modalStack.dismiss = function (modalInstance, reason) { - console.trace(); - var modalWindow = openedWindows.get(modalInstance).value; - if (modalWindow) { - modalWindow.deferred.reject(reason); - removeModalWindow(modalInstance); - } - }; - - $modalStack.getTop = function () { - return openedWindows.top(); - }; - - $modalStack.dismissAll = function () { - var modal; - while (modal = openedWindows.top()) { - $rootScope.$apply(function () { - $modalStack.dismiss(modal.key); - }); - } - } - - return $modalStack; - }]) - - .provider('$modal', function () { - - var $modalProvider = { - options: { - backdrop: true, //can be also false or 'static' - keyboard: true - }, - $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', - function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { - - var $modal = {}; - - function getTemplatePromise(options) { - return options.template ? $q.when(options.template) : - $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) { - return result.data; - }); - } - - function getResolvePromises(resolves) { - var promisesArr = []; - angular.forEach(resolves, function (value, key) { - if (angular.isFunction(value) || angular.isArray(value)) { - promisesArr.push($q.when($injector.invoke(value))); - } - }); - return promisesArr; - } - - $modal.open = function (modalOptions) { - - var modalResultDeferred = $q.defer(); - var modalOpenedDeferred = $q.defer(); - - //prepare an instance of a modal to be injected into controllers and returned to a caller - var modalInstance = { - result: modalResultDeferred.promise, - opened: modalOpenedDeferred.promise, - close: function (result) { - $modalStack.close(modalInstance, result); - }, - dismiss: function (reason) { - $modalStack.dismiss(modalInstance, reason); - } - }; - - //merge and clean up options - modalOptions = angular.extend({}, $modalProvider.options, modalOptions); - modalOptions.resolve = modalOptions.resolve || {}; - - //verify options - if (!modalOptions.template && !modalOptions.templateUrl) { - throw new Error('One of template or templateUrl options is required.'); - } - - var templateAndResolvePromise = - $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); - - - templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { - - var modalScope = (modalOptions.scope || $rootScope).$new(); - modalScope.$close = modalInstance.close; - modalScope.$dismiss = modalInstance.dismiss; - - var ctrlInstance, ctrlLocals = {}; - var resolveIter = 1; - - //controllers - if (modalOptions.controller) { - ctrlLocals.$scope = modalScope; - ctrlLocals.$modalInstance = modalInstance; - angular.forEach(modalOptions.resolve, function (value, key) { - ctrlLocals[key] = tplAndVars[resolveIter++]; - }); - - ctrlInstance = $controller(modalOptions.controller, ctrlLocals); - } - - $modalStack.open(modalInstance, { - scope: modalScope, - deferred: modalResultDeferred, - content: tplAndVars[0], - backdrop: modalOptions.backdrop, - keyboard: modalOptions.keyboard, - windowClass: modalOptions.windowClass - }); - - }, function resolveError(reason) { - modalResultDeferred.reject(reason); - }); - - templateAndResolvePromise.then(function () { - modalOpenedDeferred.resolve(true); - }, function () { - modalOpenedDeferred.reject(false); - }); - - return modalInstance; - }; - - return $modal; - }] - }; - - return $modalProvider; - }); diff --git a/app/vendor/ui-bootstrap/ui-bootstrap-custom-0.7.0.min.js b/app/vendor/ui-bootstrap/ui-bootstrap-custom-0.7.0.min.js deleted file mode 100644 index 0ed327a0..00000000 --- a/app/vendor/ui-bootstrap/ui-bootstrap-custom-0.7.0.min.js +++ /dev/null @@ -1 +0,0 @@ -angular.module("ui.bootstrap",["ui.bootstrap.modal"]),angular.module("ui.bootstrap.modal",[]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c
");d.attr("window-class",c.windowClass),d.attr("index",k.length()-1),d.html(c.content);var f=b(d)(c.scope);k.top().value.modalDomEl=f,j.append(f),e()>=0&&!h&&(g=angular.element("
"),h=b(g)(i),j.append(h))},l.close=function(a,b){var c=k.get(a);c&&(c.value.deferred.resolve(b),f(a))},l.dismiss=function(a,b){var c=k.get(a).value;c&&(c.deferred.reject(b),f(a))},l.getTop=function(){return k.top()},l}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}); \ No newline at end of file diff --git a/app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.js b/app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.js new file mode 100644 index 00000000..44b69706 --- /dev/null +++ b/app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.js @@ -0,0 +1,1024 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.10.0 - 2014-01-28 + * License: MIT + */ +angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.dropdownToggle","ui.bootstrap.transition","ui.bootstrap.modal","ui.bootstrap.position","ui.bootstrap.bindHtml","ui.bootstrap.tooltip"]); +angular.module("ui.bootstrap.tpls", ["template/modal/backdrop.html","template/modal/window.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html"]); +/* + * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js + * @restrict class or attribute + * @example: + + */ + +angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) { + var openElement = null, + closeMenu = angular.noop; + return { + restrict: 'CA', + link: function(scope, element, attrs) { + scope.$watch('$location.path', function() { closeMenu(); }); + element.parent().bind('click', function() { closeMenu(); }); + element.bind('click', function (event) { + + var elementWasOpen = (element === openElement); + + event.preventDefault(); + event.stopPropagation(); + + if (!!openElement) { + closeMenu(); + } + + if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) { + element.parent().addClass('open'); + openElement = element; + closeMenu = function (event) { + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + $document.unbind('click', closeMenu); + element.parent().removeClass('open'); + closeMenu = angular.noop; + openElement = null; + }; + $document.bind('click', closeMenu); + } + }); + } + }; +}]); + +angular.module('ui.bootstrap.transition', []) + +/** + * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. + * @param {DOMElement} element The DOMElement that will be animated. + * @param {string|object|function} trigger The thing that will cause the transition to start: + * - As a string, it represents the css class to be added to the element. + * - As an object, it represents a hash of style attributes to be applied to the element. + * - As a function, it represents a function to be called that will cause the transition to occur. + * @return {Promise} A promise that is resolved when the transition finishes. + */ +.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { + + var $transition = function(element, trigger, options) { + options = options || {}; + var deferred = $q.defer(); + var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"]; + + var transitionEndHandler = function(event) { + $rootScope.$apply(function() { + element.unbind(endEventName, transitionEndHandler); + deferred.resolve(element); + }); + }; + + if (endEventName) { + element.bind(endEventName, transitionEndHandler); + } + + // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur + $timeout(function() { + if ( angular.isString(trigger) ) { + element.addClass(trigger); + } else if ( angular.isFunction(trigger) ) { + trigger(element); + } else if ( angular.isObject(trigger) ) { + element.css(trigger); + } + //If browser does not support transitions, instantly resolve + if ( !endEventName ) { + deferred.resolve(element); + } + }); + + // Add our custom cancel function to the promise that is returned + // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, + // i.e. it will therefore never raise a transitionEnd event for that transition + deferred.promise.cancel = function() { + if ( endEventName ) { + element.unbind(endEventName, transitionEndHandler); + } + deferred.reject('Transition cancelled'); + }; + + return deferred.promise; + }; + + // Work out the name of the transitionEnd event + var transElement = document.createElement('trans'); + var transitionEndEventNames = { + 'WebkitTransition': 'webkitTransitionEnd', + 'MozTransition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'transition': 'transitionend' + }; + var animationEndEventNames = { + 'WebkitTransition': 'webkitAnimationEnd', + 'MozTransition': 'animationend', + 'OTransition': 'oAnimationEnd', + 'transition': 'animationend' + }; + function findEndEventName(endEventNames) { + for (var name in endEventNames){ + if (transElement.style[name] !== undefined) { + return endEventNames[name]; + } + } + } + $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); + $transition.animationEndEventName = findEndEventName(animationEndEventNames); + return $transition; +}]); + +angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) + +/** + * A helper, internal data structure that acts as a map but also allows getting / removing + * elements in the LIFO order + */ + .factory('$$stackedMap', function () { + return { + createNew: function () { + var stack = []; + + return { + add: function (key, value) { + stack.push({ + key: key, + value: value + }); + }, + get: function (key) { + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + return stack[i]; + } + } + }, + keys: function() { + var keys = []; + for (var i = 0; i < stack.length; i++) { + keys.push(stack[i].key); + } + return keys; + }, + top: function () { + return stack[stack.length - 1]; + }, + remove: function (key) { + var idx = -1; + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + idx = i; + break; + } + } + return stack.splice(idx, 1)[0]; + }, + removeTop: function () { + return stack.splice(stack.length - 1, 1)[0]; + }, + length: function () { + return stack.length; + } + }; + } + }; + }) + +/** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', ['$timeout', function ($timeout) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/modal/backdrop.html', + link: function (scope) { + + scope.animate = false; + + //trigger CSS transitions + $timeout(function () { + scope.animate = true; + }); + } + }; + }]) + + .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { + return { + restrict: 'EA', + scope: { + index: '@', + animate: '=' + }, + replace: true, + transclude: true, + templateUrl: 'template/modal/window.html', + link: function (scope, element, attrs) { + scope.windowClass = attrs.windowClass || ''; + + $timeout(function () { + // trigger CSS transitions + scope.animate = true; + // focus a freshly-opened modal + element[0].focus(); + }); + + scope.close = function (evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + } + }; + }]) + + .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', + function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { + + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropDomEl, backdropScope; + var openedWindows = $$stackedMap.createNew(); + var $modalStack = {}; + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex){ + if (backdropScope) { + backdropScope.index = newBackdropIndex; + } + }); + + function removeModalWindow(modalInstance) { + + var body = $document.find('body').eq(0); + var modalWindow = openedWindows.get(modalInstance).value; + + //clean up the stack + openedWindows.remove(modalInstance); + + //remove window DOM element + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, checkRemoveBackdrop); + body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); + } + + function checkRemoveBackdrop() { + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { + backdropScopeRef.$destroy(); + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } + } + + function removeAfterAnimate(domEl, scope, emulateTime, done) { + // Closing animation + scope.animate = false; + + var transitionEndEventName = $transition.transitionEndEventName; + if (transitionEndEventName) { + // transition out + var timeout = $timeout(afterAnimating, emulateTime); + + domEl.bind(transitionEndEventName, function () { + $timeout.cancel(timeout); + afterAnimating(); + scope.$apply(); + }); + } else { + // Ensure this call is async + $timeout(afterAnimating, 0); + } + + function afterAnimating() { + if (afterAnimating.done) { + return; + } + afterAnimating.done = true; + + domEl.remove(); + if (done) { + done(); + } + } + } + + $document.bind('keydown', function (evt) { + var modal; + + if (evt.which === 27) { + modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + $rootScope.$apply(function () { + $modalStack.dismiss(modal.key); + }); + } + } + }); + + $modalStack.open = function (modalInstance, modal) { + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard + }); + + var body = $document.find('body').eq(0), + currBackdropIndex = backdropIndex(); + + if (currBackdropIndex >= 0 && !backdropDomEl) { + backdropScope = $rootScope.$new(true); + backdropScope.index = currBackdropIndex; + backdropDomEl = $compile('
')(backdropScope); + body.append(backdropDomEl); + } + + var angularDomEl = angular.element('
'); + angularDomEl.attr('window-class', modal.windowClass); + angularDomEl.attr('index', openedWindows.length() - 1); + angularDomEl.attr('animate', 'animate'); + angularDomEl.html(modal.content); + + var modalDomEl = $compile(angularDomEl)(modal.scope); + openedWindows.top().value.modalDomEl = modalDomEl; + body.append(modalDomEl); + body.addClass(OPENED_MODAL_CLASS); + }; + + $modalStack.close = function (modalInstance, result) { + var modalWindow = openedWindows.get(modalInstance).value; + if (modalWindow) { + modalWindow.deferred.resolve(result); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismiss = function (modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance).value; + if (modalWindow) { + modalWindow.deferred.reject(reason); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismissAll = function (reason) { + var topModal = this.getTop(); + while (topModal) { + this.dismiss(topModal.key, reason); + topModal = this.getTop(); + } + }; + + $modalStack.getTop = function () { + return openedWindows.top(); + }; + + return $modalStack; + }]) + + .provider('$modal', function () { + + var $modalProvider = { + options: { + backdrop: true, //can be also false or 'static' + keyboard: true + }, + $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', + function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { + + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) { + return result.data; + }); + } + + function getResolvePromises(resolves) { + var promisesArr = []; + angular.forEach(resolves, function (value, key) { + if (angular.isFunction(value) || angular.isArray(value)) { + promisesArr.push($q.when($injector.invoke(value))); + } + }); + return promisesArr; + } + + $modal.open = function (modalOptions) { + + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + close: function (result) { + $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + $modalStack.dismiss(modalInstance, reason); + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + + + templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { + + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$modalInstance = modalInstance; + angular.forEach(modalOptions.resolve, function (value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + content: tplAndVars[0], + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + windowClass: modalOptions.windowClass + }); + + }, function resolveError(reason) { + modalResultDeferred.reject(reason); + }); + + templateAndResolvePromise.then(function () { + modalOpenedDeferred.resolve(true); + }, function () { + modalOpenedDeferred.reject(false); + }); + + return modalInstance; + }; + + return $modal; + }] + }; + + return $modalProvider; + }); + +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', ['$document', '$window', function ($document, $window) { + + function getStyle(el, cssprop) { + if (el.currentStyle) { //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, "position") || 'static' ) === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + var parentOffsetEl = function (element) { + var docDomEl = $document[0]; + var offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function (element) { + var elBCR = this.offset(element); + var offsetParentBCR = { top: 0, left: 0 }; + var offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft) + }; + } + }; + }]); + +angular.module('ui.bootstrap.bindHtml', []) + + .directive('bindHtmlUnsafe', function () { + return function (scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); + scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; + }); +/** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ +angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider( '$tooltip', function () { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + animation: true, + popupDelay: 0 + }; + + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'focus': 'blur' + }; + + // The options specified to the provider globally. + var globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function( value ) { + angular.extend( globalOptions, value ); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + */ + this.setTriggers = function setTriggers ( triggers ) { + angular.extend( triggerMap, triggers ); + }; + + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name){ + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) { + return function $tooltip ( type, prefix, defaultTriggerShow ) { + var options = angular.extend( {}, defaultOptions, globalOptions ); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers ( trigger ) { + var show = trigger || options.trigger || defaultTriggerShow; + var hide = triggerMap[show] || show; + return { + show: show, + hide: hide + }; + } + + var directiveName = snake_case( type ); + + var startSym = $interpolate.startSymbol(); + var endSym = $interpolate.endSymbol(); + var template = + '
'+ + '
'; + + return { + restrict: 'EA', + scope: true, + compile: function (tElem, tAttrs) { + var tooltipLinker = $compile( template ); + + return function link ( scope, element, attrs ) { + var tooltip; + var transitionTimeout; + var popupTimeout; + var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; + var triggers = getTriggers( undefined ); + var hasRegisteredTriggers = false; + var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + + var positionTooltip = function (){ + var position, + ttWidth, + ttHeight, + ttPosition; + // Get the position of the directive element. + position = appendToBody ? $position.offset( element ) : $position.position( element ); + + // Get the height and width of the tooltip so we can center it. + ttWidth = tooltip.prop( 'offsetWidth' ); + ttHeight = tooltip.prop( 'offsetHeight' ); + + // Calculate the tooltip's top and left coordinates to center it with + // this directive. + switch ( scope.tt_placement ) { + case 'right': + ttPosition = { + top: position.top + position.height / 2 - ttHeight / 2, + left: position.left + position.width + }; + break; + case 'bottom': + ttPosition = { + top: position.top + position.height, + left: position.left + position.width / 2 - ttWidth / 2 + }; + break; + case 'left': + ttPosition = { + top: position.top + position.height / 2 - ttHeight / 2, + left: position.left - ttWidth + }; + break; + default: + ttPosition = { + top: position.top - ttHeight, + left: position.left + position.width / 2 - ttWidth / 2 + }; + break; + } + + ttPosition.top += 'px'; + ttPosition.left += 'px'; + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + + }; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + scope.tt_isOpen = false; + + function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + return; + } + if ( scope.tt_popupDelay ) { + popupTimeout = $timeout( show, scope.tt_popupDelay, false ); + popupTimeout.then(function(reposition){reposition();}); + } else { + show()(); + } + } + + function hideTooltipBind () { + scope.$apply(function () { + hide(); + }); + } + + // Show the tooltip popup element. + function show() { + + + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return angular.noop; + } + + createTooltip(); + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + } + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + if ( appendToBody ) { + $document.find( 'body' ).append( tooltip ); + } else { + element.after( tooltip ); + } + + positionTooltip(); + + // And show the tooltip. + scope.tt_isOpen = true; + scope.$digest(); // digest required as $apply is not called + + // Return positioning function as promise callback for correct + // positioning after draw. + return positionTooltip; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + scope.tt_isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel( popupTimeout ); + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( scope.tt_animation ) { + transitionTimeout = $timeout(removeTooltip, 500); + } else { + removeTooltip(); + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + removeTooltip(); + } + tooltip = tooltipLinker(scope, function () {}); + + // Get contents rendered into the tooltip + scope.$digest(); + } + + function removeTooltip() { + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + } + + /** + * Observe the relevant attributes. + */ + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + + if (!val && scope.tt_isOpen ) { + hide(); + } + }); + + attrs.$observe( prefix+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( prefix+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( prefix+'PopupDelay', function ( val ) { + var delay = parseInt( val, 10 ); + scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + }); + + var unregisterTriggers = function() { + if (hasRegisteredTriggers) { + element.unbind( triggers.show, showTooltipBind ); + element.unbind( triggers.hide, hideTooltipBind ); + } + }; + + attrs.$observe( prefix+'Trigger', function ( val ) { + unregisterTriggers(); + + triggers = getTriggers( val ); + + if ( triggers.show === triggers.hide ) { + element.bind( triggers.show, toggleTooltipBind ); + } else { + element.bind( triggers.show, showTooltipBind ); + element.bind( triggers.hide, hideTooltipBind ); + } + + hasRegisteredTriggers = true; + }); + + var animation = scope.$eval(attrs[prefix + 'Animation']); + scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; + + attrs.$observe( prefix+'AppendToBody', function ( val ) { + appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; + }); + + // if a tooltip is attached to we need to remove it on + // location change as its parent scope will probably not be destroyed + // by the change. + if ( appendToBody ) { + scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { + if ( scope.tt_isOpen ) { + hide(); + } + }); + } + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + $timeout.cancel( transitionTimeout ); + $timeout.cancel( popupTimeout ); + unregisterTriggers(); + removeTooltip(); + }); + }; + } + }; + }; + }]; +}) + +.directive( 'tooltipPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' + }; +}) + +.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); +}]) + +.directive( 'tooltipHtmlUnsafePopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + }; +}) + +.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); +}]); + +angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/modal/backdrop.html", + "
"); +}]); + +angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/modal/window.html", + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
"); +}]); + +angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html", + "
\n" + + "
\n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-popup.html", + "
\n" + + "
\n" + + "
\n" + + "
\n" + + ""); +}]); diff --git a/app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.min.js b/app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.min.js new file mode 100644 index 00000000..e1c51453 --- /dev/null +++ b/app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.10.0.min.js @@ -0,0 +1,8 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.10.0 - 2014-01-28 + * License: MIT + */ +angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.dropdownToggle","ui.bootstrap.transition","ui.bootstrap.modal","ui.bootstrap.position","ui.bootstrap.bindHtml","ui.bootstrap.tooltip"]),angular.module("ui.bootstrap.tpls",["template/modal/backdrop.html","template/modal/window.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html"]),angular.module("ui.bootstrap.dropdownToggle",[]).directive("dropdownToggle",["$document","$location",function(a){var b=null,c=angular.noop;return{restrict:"CA",link:function(d,e){d.$watch("$location.path",function(){c()}),e.parent().bind("click",function(){c()}),e.bind("click",function(d){var f=e===b;d.preventDefault(),d.stopPropagation(),b&&c(),f||e.hasClass("disabled")||e.prop("disabled")||(e.parent().addClass("open"),b=e,c=function(d){d&&(d.preventDefault(),d.stopPropagation()),a.unbind("click",c),e.parent().removeClass("open"),c=angular.noop,b=null},a.bind("click",c))})}}}]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0)}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g,0)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&e.$apply(function(){o.dismiss(b.key)}))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();h>=0&&!k&&(l=e.$new(!0),l.index=h,k=d("
")(l),f.append(k));var i=angular.element("
");i.attr("window-class",b.windowClass),i.attr("index",n.length()-1),i.attr("animate","animate"),i.html(b.content);var j=d(i)(b.scope);n.top().value.modalDomEl=j,f.append(j),f.addClass(m)},o.close=function(a,b){var c=n.get(a).value;c&&(c.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a).value;c&&(c.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].body.scrollTop||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].body.scrollLeft||a[0].documentElement.scrollLeft)}}}}]),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
"+"
";return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!z||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return b.tt_content?(r(),u&&g.cancel(u),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),A(),b.tt_isOpen=!0,b.$digest(),A):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),b.tt_animation?u=g(s,500):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=!1,z=angular.isDefined(d[l+"Enable"]),A=function(){var a,d,e,f;switch(a=w?j.offset(c):j.position(c),d=t.prop("offsetWidth"),e=t.prop("offsetHeight"),b.tt_placement){case"right":f={top:a.top+a.height/2-e/2,left:a.left+a.width};break;case"bottom":f={top:a.top+a.height,left:a.left+a.width/2-d/2};break;case"left":f={top:a.top+a.height/2-e/2,left:a.left-d};break;default:f={top:a.top-e,left:a.left+a.width/2-d/2}}f.top+="px",f.left+="px",t.css(f)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)?a:o.placement}),d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var B=function(){y&&(c.unbind(x.show,k),c.unbind(x.hide,m))};d.$observe(l+"Trigger",function(a){B(),x=n(a),x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m)),y=!0});var C=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(C)?!!C:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),B(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'
\n
\n
\n
\n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'
\n
\n
\n
\n')}]); \ No newline at end of file diff --git a/app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.7.0.js b/app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.7.0.js deleted file mode 100644 index 6cb3d2ae..00000000 --- a/app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.7.0.js +++ /dev/null @@ -1,353 +0,0 @@ -angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.modal"]); -angular.module("ui.bootstrap.tpls", ["template/modal/backdrop.html","template/modal/window.html"]); -angular.module('ui.bootstrap.modal', []) - -/** - * A helper, internal data structure that acts as a map but also allows getting / removing - * elements in the LIFO order - */ - .factory('$$stackedMap', function () { - return { - createNew: function () { - var stack = []; - - return { - add: function (key, value) { - stack.push({ - key: key, - value: value - }); - }, - get: function (key) { - for (var i = 0; i < stack.length; i++) { - if (key == stack[i].key) { - return stack[i]; - } - } - }, - keys: function() { - var keys = []; - for (var i = 0; i < stack.length; i++) { - keys.push(stack[i].key); - } - return keys; - }, - top: function () { - return stack[stack.length - 1]; - }, - remove: function (key) { - var idx = -1; - for (var i = 0; i < stack.length; i++) { - if (key == stack[i].key) { - idx = i; - break; - } - } - return stack.splice(idx, 1)[0]; - }, - removeTop: function () { - return stack.splice(stack.length - 1, 1)[0]; - }, - length: function () { - return stack.length; - } - }; - } - }; - }) - -/** - * A helper directive for the $modal service. It creates a backdrop element. - */ - .directive('modalBackdrop', ['$modalStack', '$timeout', function ($modalStack, $timeout) { - return { - restrict: 'EA', - replace: true, - templateUrl: 'template/modal/backdrop.html', - link: function (scope, element, attrs) { - - //trigger CSS transitions - $timeout(function () { - scope.animate = true; - }); - - scope.close = function (evt) { - var modal = $modalStack.getTop(); - if (modal && modal.value.backdrop && modal.value.backdrop != 'static') { - evt.preventDefault(); - evt.stopPropagation(); - $modalStack.dismiss(modal.key, 'backdrop click'); - } - }; - } - }; - }]) - - .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { - return { - restrict: 'EA', - scope: { - index: '@' - }, - replace: true, - transclude: true, - templateUrl: 'template/modal/window.html', - link: function (scope, element, attrs) { - scope.windowClass = attrs.windowClass || ''; - - //trigger CSS transitions - $timeout(function () { - scope.animate = true; - }); - - scope.close = function (evt) { - var modal = $modalStack.getTop(); - if (modal) { - evt.preventDefault(); - evt.stopPropagation(); - $modalStack.dismiss(modal.key, 'backdrop click'); - } - }; - } - }; - }]) - - .factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap', - function ($document, $compile, $rootScope, $$stackedMap) { - - var backdropjqLiteEl, backdropDomEl; - var backdropScope = $rootScope.$new(true); - var body = $document.find('body').eq(0); - var openedWindows = $$stackedMap.createNew(); - var $modalStack = {}; - - function backdropIndex() { - var topBackdropIndex = -1; - var opened = openedWindows.keys(); - for (var i = 0; i < opened.length; i++) { - if (openedWindows.get(opened[i]).value.backdrop) { - topBackdropIndex = i; - } - } - return topBackdropIndex; - } - - $rootScope.$watch(backdropIndex, function(newBackdropIndex){ - backdropScope.index = newBackdropIndex; - }); - - function removeModalWindow(modalInstance) { - - var modalWindow = openedWindows.get(modalInstance).value; - - //clean up the stack - openedWindows.remove(modalInstance); - - //remove window DOM element - modalWindow.modalDomEl.remove(); - - //remove backdrop if no longer needed - if (backdropDomEl && backdropIndex() == -1) { - backdropDomEl.remove(); - backdropDomEl = undefined; - } - - //destroy scope - modalWindow.modalScope.$destroy(); - } - - $document.bind('keydown', function (evt) { - var modal; - - if (evt.which === 27) { - modal = openedWindows.top(); - if (modal && modal.value.keyboard) { - $rootScope.$apply(function () { - $modalStack.dismiss(modal.key); - }); - } - } - }); - - $modalStack.open = function (modalInstance, modal) { - - openedWindows.add(modalInstance, { - deferred: modal.deferred, - modalScope: modal.scope, - backdrop: modal.backdrop, - keyboard: modal.keyboard - }); - - var angularDomEl = angular.element('
'); - angularDomEl.attr('window-class', modal.windowClass); - angularDomEl.attr('index', openedWindows.length() - 1); - angularDomEl.html(modal.content); - - var modalDomEl = $compile(angularDomEl)(modal.scope); - openedWindows.top().value.modalDomEl = modalDomEl; - body.append(modalDomEl); - - if (backdropIndex() >= 0 && !backdropDomEl) { - backdropjqLiteEl = angular.element('
'); - backdropDomEl = $compile(backdropjqLiteEl)(backdropScope); - body.append(backdropDomEl); - } - }; - - $modalStack.close = function (modalInstance, result) { - var modal = openedWindows.get(modalInstance); - if (modal) { - modal.value.deferred.resolve(result); - removeModalWindow(modalInstance); - } - }; - - $modalStack.dismiss = function (modalInstance, reason) { - var modalWindow = openedWindows.get(modalInstance).value; - if (modalWindow) { - modalWindow.deferred.reject(reason); - removeModalWindow(modalInstance); - } - }; - - $modalStack.dismissAll = function () { - var modal; - while (openedWindows.length()) { - if (modal = openedWindows.top()) { - $modalStack.dismiss(modal.key); - } - } - } - - $modalStack.getTop = function () { - return openedWindows.top(); - }; - - return $modalStack; - }]) - - .provider('$modal', function () { - - var $modalProvider = { - options: { - backdrop: true, //can be also false or 'static' - keyboard: true - }, - $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', - function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { - - var $modal = {}; - - function getTemplatePromise(options) { - return options.template ? $q.when(options.template) : - $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) { - return result.data; - }); - } - - function getResolvePromises(resolves) { - var promisesArr = []; - angular.forEach(resolves, function (value, key) { - if (angular.isFunction(value) || angular.isArray(value)) { - promisesArr.push($q.when($injector.invoke(value))); - } - }); - return promisesArr; - } - - $modal.open = function (modalOptions) { - - var modalResultDeferred = $q.defer(); - var modalOpenedDeferred = $q.defer(); - - //prepare an instance of a modal to be injected into controllers and returned to a caller - var modalInstance = { - result: modalResultDeferred.promise, - opened: modalOpenedDeferred.promise, - close: function (result) { - $modalStack.close(modalInstance, result); - }, - dismiss: function (reason) { - $modalStack.dismiss(modalInstance, reason); - } - }; - - //merge and clean up options - modalOptions = angular.extend({}, $modalProvider.options, modalOptions); - modalOptions.resolve = modalOptions.resolve || {}; - - //verify options - if (!modalOptions.template && !modalOptions.templateUrl) { - throw new Error('One of template or templateUrl options is required.'); - } - - var templateAndResolvePromise = - $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); - - - templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { - - var modalScope = (modalOptions.scope || $rootScope).$new(); - modalScope.$close = modalInstance.close; - modalScope.$dismiss = modalInstance.dismiss; - - var ctrlInstance, ctrlLocals = {}; - var resolveIter = 1; - - //controllers - if (modalOptions.controller) { - ctrlLocals.$scope = modalScope; - ctrlLocals.$modalInstance = modalInstance; - angular.forEach(modalOptions.resolve, function (value, key) { - ctrlLocals[key] = tplAndVars[resolveIter++]; - }); - - ctrlInstance = $controller(modalOptions.controller, ctrlLocals); - } - - $modalStack.open(modalInstance, { - scope: modalScope, - deferred: modalResultDeferred, - content: tplAndVars[0], - backdrop: modalOptions.backdrop, - keyboard: modalOptions.keyboard, - windowClass: modalOptions.windowClass - }); - - }, function resolveError(reason) { - modalResultDeferred.reject(reason); - }); - - templateAndResolvePromise.then(function () { - modalOpenedDeferred.resolve(true); - }, function () { - modalOpenedDeferred.reject(false); - }); - - return modalInstance; - }; - - return $modal; - }] - }; - - return $modalProvider; - }); - -angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/modal/backdrop.html", - "
"); -}]); - -angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) { - $templateCache.put("template/modal/window.html", - "
\ -
\ -
\ -
\ -
\ -
\ -
\ -
\ -
"); -}]); diff --git a/app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.7.0.min.js b/app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.7.0.min.js deleted file mode 100644 index ac9cb86b..00000000 --- a/app/vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.7.0.min.js +++ /dev/null @@ -1 +0,0 @@ -angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.modal"]),angular.module("ui.bootstrap.tpls",["template/modal/backdrop.html","template/modal/window.html"]),angular.module("ui.bootstrap.modal",[]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c
");d.attr("window-class",c.windowClass),d.attr("index",k.length()-1),d.html(c.content);var f=b(d)(c.scope);k.top().value.modalDomEl=f,j.append(f),e()>=0&&!h&&(g=angular.element("
"),h=b(g)(i),j.append(h))},l.close=function(a,b){var c=k.get(a);c&&(c.value.deferred.resolve(b),f(a))},l.dismiss=function(a,b){var c=k.get(a).value;c&&(c.deferred.reject(b),f(a))},l.getTop=function(){return k.top()},l}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'')}]); \ No newline at end of file