提交 d178092c authored 作者: Jonatas Oliveira's avatar Jonatas Oliveira 提交者: Italo Rossi

Creating Verto Communicator.

Verto Communicator is a web interface built on top of Verto and AngularJS.

Brought to you by Evolux Sistemas and FreeSWITCH team. :)

FS-7795 - implements fullscreen menu and doubleclick function.
FS-7795 - added chat icon on fullscreen video
FS-7796 - fix missing tooltips in call icons
FS-7796 - fix tooltip position
FS-7798 - implements change login information in modal view
FS-7828 - fix esc key bug when leave fullscren mode. Using css instead of javascript in fullscreen for elements manipulation.
FS-7826 - fix chat sender id with name instead of extension
FS-7831 - remove demo from title
FS-7841 - fix compatibility verification
FS-7842 - 'settings' data persistent
FS-7859 - moved popup down
FS-7827 - added screen share functionality
FS-7857 - default name for source media
FS-7879 - prompt before logout [incall]
FS-7873 - querystring for autocall
FS-7875 - persist login and password password
FS-7877 - phone feature: hold, transfer, incoming, answer, decline, call direction in history
FS-7878 - small devices
FS-7881 - added modal dialog for contributors
上级 240cfef4
......@@ -1166,6 +1166,154 @@
var CONFMAN_SERNO = 1;
/*
Conference Manager without jQuery table.
*/
$.verto.conf = function(verto, params) {
var conf = this;
conf.params = $.extend({
dialog: null,
hasVid: false,
laData: null,
onBroadcast: null,
onLaChange: null,
onLaRow: null
}, params);
conf.verto = verto;
conf.serno = CONFMAN_SERNO++;
createMainModeratorMethods();
verto.subscribe(conf.params.laData.modChannel, {
handler: function(v, e) {
if (conf.params.onBroadcast) {
conf.params.onBroadcast(verto, conf, e.data);
}
}
})
};
$.verto.conf.prototype.modCommand = function(cmd, id, value) {
var conf = this;
conf.verto.rpcClient.call("verto.broadcast", {
"eventChannel": conf.params.laData.modChannel,
"data": {
"application": "conf-control",
"command": cmd,
"id": id,
"value": value
}
});
};
$.verto.conf.prototype.destroy = function() {
var conf = this;
conf.destroyed = true;
conf.params.onBroadcast(verto, conf, 'destroy');
if (conf.params.laData.modChannel) {
conf.verto.unsubscribe(conf.params.laData.modChannel);
}
};
function createMainModeratorMethods() {
$.verto.conf.prototype.listVideoLayouts = function() {
this.modCommand("list-videoLayouts", null, null);
};
$.verto.conf.prototype.play = function(file) {
this.modCommand("play", null, file);
};
$.verto.conf.prototype.stop = function() {
this.modCommand("stop", null, "all");
};
$.verto.conf.prototype.record = function(file) {
this.modCommand("recording", null, ["start", file]);
};
$.verto.conf.prototype.stopRecord = function() {
this.modCommand("recording", null, ["stop", "all"]);
};
$.verto.conf.prototype.snapshot = function(file) {
if (!this.params.hasVid) {
throw 'Conference has no video';
}
this.modCommand("vid-write-png", null, file);
};
$.verto.conf.prototype.setVideoLayout = function(layout) {
if (!this.params.hasVid) {
throw 'Conference has no video';
}
this.modCommand("vid-layout", null, layout);
};
$.verto.conf.prototype.kick = function(memberID) {
this.modCommand("kick", parseInt(memberID));
};
$.verto.conf.prototype.muteMic = function(memberID) {
this.modCommand("tmute", parseInt(memberID));
};
$.verto.conf.prototype.muteVideo = function(memberID) {
if (!this.params.hasVid) {
throw 'Conference has no video';
}
this.modCommand("tvmute", parseInt(memberID));
};
$.verto.conf.prototype.presenter = function(memberID) {
if (!this.params.hasVid) {
throw 'Conference has no video';
}
this.modCommand("vid-res-id", parseInt(memberID), "presenter");
};
$.verto.conf.prototype.videoFloor = function(memberID) {
if (!this.params.hasVid) {
throw 'Conference has no video';
}
this.modCommand("vid-floor", parseInt(memberID), "force");
};
$.verto.conf.prototype.banner = function(memberID, text) {
if (!this.params.hasVid) {
throw 'Conference has no video';
}
this.modCommand("vid-banner", parseInt(memberID), escape(text));
};
$.verto.conf.prototype.volumeDown = function(memberID) {
if (!this.params.hasVid) {
throw 'Conference has no video';
}
this.modCommand("volume_in", parseInt(memberID), "down");
};
$.verto.conf.prototype.volumeUp = function(memberID) {
if (!this.params.hasVid) {
throw 'Conference has no video';
}
this.modCommand("volume_in", parseInt(memberID), "up");
};
$.verto.conf.prototype.transfer = function(memberID, exten) {
if (!this.params.hasVid) {
throw 'Conference has no video';
}
this.modCommand("transfer", parseInt(memberID), "exten");
};
}
$.verto.modfuncs = {};
$.verto.confMan = function(verto, params) {
......
[
"Jonatas Oliveira <jonatas@evolux.net.br>",
"Ítalo Rossi <italo@evolux.net.br>",
"Stefan Yohansson <stefan@evolux.net.br>"
]
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
.nthChildTest > div:nth-child(odd) {
display: none;
}
.jsDataTable > thead > tr > th {
border-width:4px;
padding: 2px;
font-size:10pt;
text-align: left;
}
.jsDataTable > thead > tr > th.notSortable {
padding: 5px;
}
.jsDataTable > tbody > tr > td {
border-bottom: 1px solid #ccc;
padding: 2px;
vertical-align: middle;
height:25px;
font-size: 10px;
}
.jsDataTable {
font-family: verdana;
font-size:10pt;
}
.jsDataTable > tbody > tr:nth-child(odd),
.jsDataTable > tbody > tr.odd {
background-color: #ffffee;
}
.jsDataTable > tbody > tr:nth-child(even),
.jsDataTable > tbody > tr.even {
background-color: #ffffff;
}
.jsDataTable > thead th.sortAsc,
.jsDataTable > thead th.sortDesc {
color:ffffff;
background-color: #7777ff;
background-position: right center;
background-repeat: no-repeat;
}
.jsDataTable > thead th.sortAsc {
background-image: url(/images/table/asc.png);
}
.jsDataTable > thead th.sortDesc {
background-image: url(/images/table/desc.png);
}
.jsDataTable.clickable > tbody > tr,
.clickable > .jsDataTable > tbody > tr {
cursor: pointer;
}
.jsDataTable.clickable > tbody > tr.nonDataRow,
.clickable > .jsDataTable > tbody > tr.nonDataRow {
cursor: auto;
}
This diff was suppressed by a .gitattributes entry.
.withripple {
position: relative;
}
.ripple-wrapper {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
overflow: hidden;
border-radius: inherit;
pointer-events: none;
}
.ripple {
position: absolute;
width: 20px;
height: 20px;
margin-left: -10px;
margin-top: -10px;
border-radius: 100%;
background-color: rgba(0, 0, 0, 0.05);
-webkit-transform: scale(1);
-ms-transform: scale(1);
transform: scale(1);
-webkit-transform-origin: 50%;
-ms-transform-origin: 50%;
transform-origin: 50%;
opacity: 0;
pointer-events: none;
}
.ripple.ripple-on {
transition: opacity 0.15s ease-in 0s, -webkit-transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
transition: opacity 0.15s ease-in 0s, transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s;
opacity: 0.1;
}
.ripple.ripple-out {
transition: opacity 0.1s linear 0s !important;
opacity: 0;
}
/*# sourceMappingURL=ripples.css.map */
\ No newline at end of file
This diff was suppressed by a .gitattributes entry.
@font-face {
font-family: 'RobotoDraft';
font-style: normal;
font-weight: 400;
src: local('RobotoDraft'), local('RobotoDraft-Regular'), local('Roboto-Regular'), url(../fonts/RobotoDraftRegular.woff2) format('woff2'), url(../fonts/RobotoDraftRegular.woff) format('woff');
}
@font-face {
font-family: 'RobotoDraft';
font-style: normal;
font-weight: 500;
src: local('RobotoDraft Medium'), local('RobotoDraft-Medium'), local('Roboto-Medium'), url(../fonts/RobotoDraftMedium.woff2) format('woff2'), url(../fonts/RobotoDraftMedium.woff) format('woff');
}
@font-face {
font-family: 'RobotoDraft';
font-style: normal;
font-weight: 700;
src: local('RobotoDraft Bold'), local('RobotoDraft-Bold'), local('Roboto-Bold'), url(../fonts/RobotoDraftBold.woff2) format('woff2'), url(../fonts/RobotoDraftBold.woff) format('woff');
}
@font-face {
font-family: 'RobotoDraft';
font-style: italic;
font-weight: 400;
src: local('RobotoDraft Italic'), local('RobotoDraft-Italic'), local('Roboto-Italic'), url(../fonts/RobotoDraftItalic.woff2) format('woff2'), url(../fonts/RobotoDraftItalic.woff) format('woff');
}
/*# sourceMappingURL=roboto.css.map */
\ No newline at end of file
This diff was suppressed by a .gitattributes entry.
差异被折叠。
This diff was suppressed by a .gitattributes entry.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
<!DOCTYPE html>
<html ng-app="vertoApp" ng-controller="MainController" lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="description" content="Verto (VER-to) RTC is a FreeSWITCH endpoint that implements a subset of a JSON-RPC connection designed for use over secure websockets.">
<meta name="author" content="FreeSWITCH">
<link rel="icon" href="favicon.ico">
<title ng-bind="'[' + title + '] ' + 'FreeSWITCH Verto&trade; Video Transcoding'"></title>
<!-- CSS -->
<link rel="stylesheet" type="text/css" href="css/bootstrap/bootstrap.min.css">
<!--<link rel="stylesheet" type="text/css" href="css/jquery.mobile.min.css">-->
<!--<link rel="stylesheet" type="text/css" href="css/jsontable.css">-->
<link rel="stylesheet" type="text/css" href="css/material-design/roboto.min.css">
<link rel="stylesheet" type="text/css" href="css/material-design/material.min.css">
<link rel="stylesheet" type="text/css" href="css/material-design/material-fullpalette.min.css">
<link rel="stylesheet" type="text/css" href="css/material-design/ripples.min.css">
<link rel="stylesheet" type="text/css" href="css/angular-toastr/angular-toastr.min.css">
<link rel="stylesheet" type="text/css" href="css/angular-tooltips.min.css">
<link rel="stylesheet" type="text/css" href="css/verto.css">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div ng-include="'partials/menu.html'"></div>
<div id="wrapper" class="toggled">
<!-- Sidebar -->
<div id="sidebar-wrapper">
<div ng-include="'partials/chat.html'"></div>
</div>
<!-- /#sidebar-wrapper -->
<!-- Page Content -->
<div id="page-content-wrapper">
<div class="container-fluid">
<div class="row" ng-view>
</div>
</div>
</div>
</div>
<video class="hide" id="webcam" autoplay="autoplay" style="width:100%; height:100%; object-fit:inherit;"></video>
<script type="text/javascript" src="js/3rd-party/getScreenId.js"></script>
<script type="text/javascript" src="js/jquery/jquery-2.1.1.min.js"></script>
<!--<script type="text/javascript" src="js/jquery/jquery.mobile.min.js"></script>-->
<script type="text/javascript" src="js/jquery/jquery.json-2.4.min.js"></script>
<script type="text/javascript" src="js/jquery/jquery.cookie.js"></script>
<script type="text/javascript" src="js/jquery/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="js/bootstrap/bootstrap.min.js"></script>
<script type="text/javascript" src="js/material-design/material.min.js"></script>
<script type="text/javascript" src="js/material-design/ripples.min.js"></script>
<script type="text/javascript" src="js/angularjs/angular.min.js"></script>
<script type="text/javascript" src="js/angularjs/angular-animate.min.js"></script>
<script type="text/javascript" src="js/angularjs/angular-route.min.js"></script>
<script type="text/javascript" src="js/angularjs/angular-cookies.min.js"></script>
<script type="text/javascript" src="js/angularui/ui-bootstrap-tpls-0.13.0.min.js"></script>
<script type="text/javascript" src="js/angularjs/angular-timer-all.min.js"></script>
<script type="text/javascript" src="js/angularjs/angular-storage.min.js"></script>
<script type="text/javascript" src="js/angularjs/angular-fullscreen.js"></script>
<script type="text/javascript" src="js/angularjs/angular-prompt.js"></script>
<script type="text/javascript" src="js/angularjs/angular-tooltips.min.js"></script>
<script type="text/javascript" src="js/angular-toastr/angular-toastr.tpls.min.js"></script>
<script type="text/javascript" src="js/angular-gravatar/angular-gravatar.js"></script>
<script type="text/javascript" src="../js/src/jquery.jsonrpcclient.js"></script>
<script type="text/javascript" src="../js/src/jquery.FSRTC.js"></script>
<script type="text/javascript" src="../js/src/jquery.verto.js"></script>
<script type="text/javascript" src="js/3rd-party/md5.min.js"></script>
<script type="text/javascript" src="js/filters.js"></script>
<script type="text/javascript" src="js/verto-service.js"></script>
<script type="text/javascript" src="js/storage-service.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<script type="text/javascript" src="js/controllers.js"></script>
<script type="text/javascript" src="js/directives.js"></script>
</body>
</html>
// Last time updated at Sep 07, 2014, 08:32:23
// Latest file can be found here: https://cdn.webrtc-experiment.com/getScreenId.js
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// Documentation - https://github.com/muaz-khan/WebRTC-Experiment/tree/master/getScreenId.js
// ______________
// getScreenId.js
/*
getScreenId(function (error, sourceId, screen_constraints) {
// error == null || 'permission-denied' || 'not-installed' || 'installed-disabled' || 'not-chrome'
// sourceId == null || 'string' || 'firefox'
if(sourceId == 'firefox') {
navigator.mozGetUserMedia(screen_constraints, onSuccess, onFailure);
}
else navigator.webkitGetUserMedia(screen_constraints, onSuccess, onFailure);
});
*/
(function() {
window.getScreenId = function(callback) {
// for Firefox:
// sourceId == 'firefox'
// screen_constraints = {...}
if (!!navigator.mozGetUserMedia) {
callback(null, 'firefox', {
video: {
mozMediaSource: 'window',
mediaSource: 'window'
}
});
return;
}
postMessage();
window.addEventListener('message', onIFrameCallback);
function onIFrameCallback(event) {
if (!event.data) return;
if (event.data.chromeMediaSourceId) {
if (event.data.chromeMediaSourceId === 'PermissionDeniedError') {
callback('permission-denied');
} else callback(null, event.data.chromeMediaSourceId, getScreenConstraints(null, event.data.chromeMediaSourceId));
}
if (event.data.chromeExtensionStatus) {
callback(event.data.chromeExtensionStatus, null, getScreenConstraints(event.data.chromeExtensionStatus));
}
// this event listener is no more needed
window.removeEventListener('message', onIFrameCallback);
}
};
function getScreenConstraints(error, sourceId) {
var screen_constraints = {
audio: false,
video: {
mandatory: {
chromeMediaSource: error ? 'screen' : 'desktop',
maxWidth: window.screen.width > 1920 ? window.screen.width : 1920,
maxHeight: window.screen.height > 1080 ? window.screen.height : 1080
},
optional: []
}
};
if (sourceId) {
screen_constraints.video.mandatory.chromeMediaSourceId = sourceId;
}
return screen_constraints;
}
function postMessage() {
if (!iframe.isLoaded) {
setTimeout(postMessage, 100);
return;
}
iframe.contentWindow.postMessage({
captureSourceId: true
}, '*');
}
var iframe = document.createElement('iframe');
iframe.onload = function() {
iframe.isLoaded = true;
};
iframe.src = 'https://www.webrtc-experiment.com/getSourceId/';
iframe.style.display = 'none';
(document.body || document.documentElement).appendChild(iframe);
})();
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
/**
* @license AngularJS v1.3.15
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
/**
* @ngdoc module
* @name ngCookies
* @description
*
* # ngCookies
*
* The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies.
*
*
* <div doc-module-components="ngCookies"></div>
*
* See {@link ngCookies.$cookies `$cookies`} and
* {@link ngCookies.$cookieStore `$cookieStore`} for usage.
*/
angular.module('ngCookies', ['ng']).
/**
* @ngdoc service
* @name $cookies
*
* @description
* Provides read/write access to browser's cookies.
*
* Only a simple Object is exposed and by adding or removing properties to/from this object, new
* cookies are created/deleted at the end of current $eval.
* The object's properties can only be strings.
*
* Requires the {@link ngCookies `ngCookies`} module to be installed.
*
* @example
*
* ```js
* angular.module('cookiesExample', ['ngCookies'])
* .controller('ExampleController', ['$cookies', function($cookies) {
* // Retrieving a cookie
* var favoriteCookie = $cookies.myFavorite;
* // Setting a cookie
* $cookies.myFavorite = 'oatmeal';
* }]);
* ```
*/
factory('$cookies', ['$rootScope', '$browser', function($rootScope, $browser) {
var cookies = {},
lastCookies = {},
lastBrowserCookies,
runEval = false,
copy = angular.copy,
isUndefined = angular.isUndefined;
//creates a poller fn that copies all cookies from the $browser to service & inits the service
$browser.addPollFn(function() {
var currentCookies = $browser.cookies();
if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
lastBrowserCookies = currentCookies;
copy(currentCookies, lastCookies);
copy(currentCookies, cookies);
if (runEval) $rootScope.$apply();
}
})();
runEval = true;
//at the end of each eval, push cookies
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
// strings or browser refuses to store some cookies, we update the model in the push fn.
$rootScope.$watch(push);
return cookies;
/**
* Pushes all the cookies from the service to the browser and verifies if all cookies were
* stored.
*/
function push() {
var name,
value,
browserCookies,
updated;
//delete any cookies deleted in $cookies
for (name in lastCookies) {
if (isUndefined(cookies[name])) {
$browser.cookies(name, undefined);
}
}
//update all cookies updated in $cookies
for (name in cookies) {
value = cookies[name];
if (!angular.isString(value)) {
value = '' + value;
cookies[name] = value;
}
if (value !== lastCookies[name]) {
$browser.cookies(name, value);
updated = true;
}
}
//verify what was actually stored
if (updated) {
updated = false;
browserCookies = $browser.cookies();
for (name in cookies) {
if (cookies[name] !== browserCookies[name]) {
//delete or reset all cookies that the browser dropped from $cookies
if (isUndefined(browserCookies[name])) {
delete cookies[name];
} else {
cookies[name] = browserCookies[name];
}
updated = true;
}
}
}
}
}]).
/**
* @ngdoc service
* @name $cookieStore
* @requires $cookies
*
* @description
* Provides a key-value (string-object) storage, that is backed by session cookies.
* Objects put or retrieved from this storage are automatically serialized or
* deserialized by angular's toJson/fromJson.
*
* Requires the {@link ngCookies `ngCookies`} module to be installed.
*
* @example
*
* ```js
* angular.module('cookieStoreExample', ['ngCookies'])
* .controller('ExampleController', ['$cookieStore', function($cookieStore) {
* // Put cookie
* $cookieStore.put('myFavorite','oatmeal');
* // Get cookie
* var favoriteCookie = $cookieStore.get('myFavorite');
* // Removing a cookie
* $cookieStore.remove('myFavorite');
* }]);
* ```
*/
factory('$cookieStore', ['$cookies', function($cookies) {
return {
/**
* @ngdoc method
* @name $cookieStore#get
*
* @description
* Returns the value of given cookie key
*
* @param {string} key Id to use for lookup.
* @returns {Object} Deserialized cookie value.
*/
get: function(key) {
var value = $cookies[key];
return value ? angular.fromJson(value) : value;
},
/**
* @ngdoc method
* @name $cookieStore#put
*
* @description
* Sets a value for given cookie key
*
* @param {string} key Id for the `value`.
* @param {Object} value Value to be stored.
*/
put: function(key, value) {
$cookies[key] = angular.toJson(value);
},
/**
* @ngdoc method
* @name $cookieStore#remove
*
* @description
* Remove given cookie
*
* @param {string} key Id of the key-value pair to delete.
*/
remove: function(key) {
delete $cookies[key];
}
};
}]);
})(window, window.angular);
\ No newline at end of file
This diff was suppressed by a .gitattributes entry.
(function(window) {
var createModule = function(angular) {
var module = angular.module('FBAngular', []);
module.factory('Fullscreen', ['$document', '$rootScope', function ($document,$rootScope) {
var document = $document[0];
// ensure ALLOW_KEYBOARD_INPUT is available and enabled
var isKeyboardAvailbleOnFullScreen = (typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element) && Element.ALLOW_KEYBOARD_INPUT;
var emitter = $rootScope.$new();
// listen event on document instead of element to avoid firefox limitation
// see https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Using_full_screen_mode
$document.on('fullscreenchange webkitfullscreenchange mozfullscreenchange MSFullscreenChange', function(){
emitter.$emit('FBFullscreen.change', serviceInstance.isEnabled());
});
var serviceInstance = {
$on: angular.bind(emitter, emitter.$on),
all: function() {
serviceInstance.enable( document.documentElement );
},
enable: function(element) {
if(element.requestFullScreen) {
element.requestFullScreen();
} else if(element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if(element.webkitRequestFullscreen) {
// Safari temporary fix
if (/Version\/[\d]{1,2}(\.[\d]{1,2}){1}(\.(\d){1,2}){0,1} Safari/.test(navigator.userAgent)) {
element.webkitRequestFullscreen();
} else {
element.webkitRequestFullscreen(isKeyboardAvailbleOnFullScreen);
}
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
},
cancel: function() {
if(document.cancelFullScreen) {
document.cancelFullScreen();
} else if(document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if(document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
},
isEnabled: function(){
var fullscreenElement = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement;
return fullscreenElement ? true : false;
},
toggleAll: function(){
serviceInstance.isEnabled() ? serviceInstance.cancel() : serviceInstance.all();
},
isSupported: function(){
var docElm = document.documentElement;
var requestFullscreen = docElm.requestFullScreen || docElm.mozRequestFullScreen || docElm.webkitRequestFullscreen || docElm.msRequestFullscreen;
return requestFullscreen ? true : false;
}
};
return serviceInstance;
}]);
module.directive('fullscreen', ['Fullscreen', function(Fullscreen) {
return {
link : function ($scope, $element, $attrs) {
// Watch for changes on scope if model is provided
if ($attrs.fullscreen) {
$scope.$watch($attrs.fullscreen, function(value) {
var isEnabled = Fullscreen.isEnabled();
if (value && !isEnabled) {
Fullscreen.enable($element[0]);
$element.addClass('isInFullScreen');
} else if (!value && isEnabled) {
Fullscreen.cancel();
$element.removeClass('isInFullScreen');
}
});
// Listen on the `FBFullscreen.change`
// the event will fire when anything changes the fullscreen mode
var removeFullscreenHandler = Fullscreen.$on('FBFullscreen.change', function(evt, isFullscreenEnabled){
if(!isFullscreenEnabled){
$scope.$evalAsync(function(){
$scope.$eval($attrs.fullscreen + '= false');
$element.removeClass('isInFullScreen');
});
}
});
$scope.$on('$destroy', function() {
removeFullscreenHandler();
});
} else {
if ($attrs.onlyWatchedProperty !== undefined) {
return;
}
$element.on('click', function (ev) {
Fullscreen.enable( $element[0] );
});
}
}
};
}]);
return module;
};
if (typeof define === "function" && define.amd) {
define("FBAngular", ['angular'], function (angular) { return createModule(angular); } );
} else {
createModule(window.angular);
}
})(window);
angular.module('cgPrompt',['ui.bootstrap']);
angular.module('cgPrompt').factory('prompt',['$modal','$q',function($modal,$q){
var prompt = function(options){
var defaults = {
title: '',
message: '',
input: false,
label: '',
value: '',
values: false,
buttons: [
{label:'Cancel',cancel:true},
{label:'OK',primary:true}
]
};
if (options === undefined){
options = {};
}
for (var key in defaults) {
if (options[key] === undefined) {
options[key] = defaults[key];
}
}
var defer = $q.defer();
$modal.open({
templateUrl:'partials/angular-prompt.html',
controller: 'cgPromptCtrl',
resolve: {
options:function(){
return options;
}
}
}).result.then(function(result){
if (options.input){
defer.resolve(result.input);
} else {
defer.resolve(result.button);
}
}, function(){
defer.reject();
});
return defer.promise;
};
return prompt;
}
]);
angular.module('cgPrompt').controller('cgPromptCtrl',['$scope','options','$timeout',function($scope,options,$timeout){
$scope.input = {name:options.value};
$scope.options = options;
$scope.buttonClicked = function(button){
if (button.cancel){
$scope.$dismiss();
return;
}
if (options.input && angular.element(document.querySelector('#cgPromptForm')).scope().cgPromptForm.$invalid){
$scope.changed = true;
return;
}
$scope.$close({button:button,input:$scope.input.name});
};
$scope.submit = function(){
var ok;
angular.forEach($scope.options.buttons,function(button){
if (button.primary){
ok = button;
}
});
if (ok){
$scope.buttonClicked(ok);
}
};
$timeout(function(){
var elem = document.querySelector('#cgPromptInput');
if (elem) {
if (elem.select) {
elem.select();
}
if (elem.focus) {
elem.focus();
}
}
},100);
}]);
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
'use strict';
/* App Module */
var vertoApp = angular.module('vertoApp', [
'timer',
'ngRoute',
'vertoControllers',
'vertoDirectives',
'vertoFilters',
'ngStorage',
'ngAnimate',
'toastr',
'FBAngular',
'cgPrompt',
'720kb.tooltips',
'ui.gravatar',
]);
vertoApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/login', {
title: 'Login',
templateUrl: 'partials/login.html',
controller: 'LoginController'
}).
when('/dialpad', {
title: 'Dialpad',
templateUrl: 'partials/dialpad.html',
controller: 'DialPadController'
}).
when('/incall', {
title: 'In a Call',
templateUrl: 'partials/incall.html',
controller: 'InCallController'
}).
/*when('/contributors', {
title: 'Contributors',
templateUrl: 'partials/contributors.html',
controller: 'ContributorsController',
}).*/
when('/browser-upgrade', {
title: '',
templateUrl: 'partials/browser_upgrade.html',
controller: 'BrowserUpgradeController'
}).
otherwise({
redirectTo: '/login'
});
}]);
vertoApp.run(['$rootScope', '$location', 'toastr', 'prompt',
function($rootScope, $location, toastr, prompt) {
$rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
$rootScope.title = current.$$route.title;
});
$rootScope.safeProtocol = false;
if(window.location.protocol == 'https:') {
$rootScope.safeProtocol = true;
}
$rootScope.checkBrowser = function() {
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia;
if (!navigator.getUserMedia) {
$location.path('/browser-upgrade');
}
};
$rootScope.promptInput = function(title, message, label, callback) {
var ret = prompt({
title: title,
message: message,
input: true,
label: label
}).then(function (ret) {
if (angular.isFunction(callback)) {
callback(ret);
}
}, function() {
});
};
}]);
This diff was suppressed by a .gitattributes entry.
'use strict';
/* Directives */
var vertoDirectives = angular.module('vertoDirectives', []);
/**
* To RTC work properly we need to give a <video> tag as soon as possible
* because it needs to attach the video and audio stream to the tag.
*
* This directive is responsible for moving the video tag from the body to
* the right place when a call start and move back to the body when the
* call ends. It also hides and display the tag when its convenient.
*/
vertoDirectives.directive('videoTag',
function() {
function link(scope, element, attrs) {
// Moving the video tag to the new place inside the incall page.
console.log('Moving the video to element.');
jQuery('video').removeClass('hide').appendTo(element);
jQuery('video').css('display','block');
scope.callActive();
element.on('$destroy', function() {
// Move the video back to the body.
console.log('Moving the video back to body.');
jQuery('video').addClass('hide').appendTo(jQuery('body'));
});
}
return {
link: link
}
});
vertoDirectives.directive('userStatus',
function() {
var link = function(scope, element, attrs) {
scope.$watch('condition', function(condition) {
element.removeClass('connected');
element.removeClass('disconnected');
element.removeClass('connecting');
element.addClass(condition);
});
}
return {
scope: {
'condition': '='
},
link: link
};
});
vertoDirectives.directive('showControls',
function(Fullscreen) {
var link = function(scope, element, attrs) {
var i = null;
jQuery('.video-footer').fadeIn('slow');
jQuery('.video-hover-buttons').fadeIn('slow');
element.parent().bind('mousemove', function() {
if(Fullscreen.isEnabled()) {
clearTimeout(i);
jQuery('.video-footer').fadeIn('slow');
jQuery('.video-hover-buttons').fadeIn(500);
i = setTimeout(function () {
if(Fullscreen.isEnabled()) {
jQuery('.video-footer').fadeOut('slow');
jQuery('.video-hover-buttons').fadeOut(500);
}
}, 3000);
}
});
element.parent().bind('mouseleave', function () {
jQuery('.video-footer').fadeIn();
jQuery('.video-hover-buttons').fadeIn();
});
}
return {
link: link
};
});
'use strict';
/* Filters */
var vertoFilters = angular.module('vertoFilters', []);
vertoFilters.filter('gravatar',
function() {
return function (email, size) {
if (angular.isUndefined(size)) {
size = 40;
}
var hash = md5(email);
return 'https://secure.gravatar.com/avatar/' + hash + '?s=' + size + '&d=mm';
}
});
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
/*!
* jQuery Cookie Plugin v1.3.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
(function ($, document, undefined) {
var pluses = /\+/g;
function raw(s) {
return s;
}
function decoded(s) {
return unRfc2068(decodeURIComponent(s.replace(pluses, ' ')));
}
function unRfc2068(value) {
if (value.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape
value = value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
return value;
}
function fromJSON(value) {
return config.json ? JSON.parse(value) : value;
}
var config = $.cookie = function (key, value, options) {
// write
if (value !== undefined) {
options = $.extend({}, config.defaults, options);
if (value === null) {
options.expires = -1;
}
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setDate(t.getDate() + days);
}
value = config.json ? JSON.stringify(value) : String(value);
return (document.cookie = [
encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// read
var decode = config.raw ? raw : decoded;
var cookies = document.cookie.split('; ');
var result = key ? null : {};
for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = decode(parts.join('='));
if (key && key === name) {
result = fromJSON(cookie);
break;
}
if (!key) {
result[name] = fromJSON(cookie);
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) !== null) {
$.cookie(key, null, options);
return true;
}
return false;
};
})(jQuery, document);
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
/* globals jQuery */
(function($) {
// Selector to select only not already processed elements
$.expr[":"].notmdproc = function(obj){
if ($(obj).data("mdproc")) {
return false;
} else {
return true;
}
};
function _isChar(evt) {
if (typeof evt.which == "undefined") {
return true;
} else if (typeof evt.which == "number" && evt.which > 0) {
return !evt.ctrlKey && !evt.metaKey && !evt.altKey && evt.which != 8 && evt.which != 9;
}
return false;
}
$.material = {
"options": {
// These options set what will be started by $.material.init()
"input": true,
"ripples": true,
"checkbox": true,
"togglebutton": true,
"radio": true,
"arrive": true,
"autofill": false,
"withRipples": [
".btn:not(.btn-link)",
".card-image",
".navbar a:not(.withoutripple)",
".dropdown-menu a",
".nav-tabs a:not(.withoutripple)",
".withripple"
].join(","),
"inputElements": "input.form-control, textarea.form-control, select.form-control",
"checkboxElements": ".checkbox > label > input[type=checkbox]",
"togglebuttonElements": ".togglebutton > label > input[type=checkbox]",
"radioElements": ".radio > label > input[type=radio]"
},
"checkbox": function(selector) {
// Add fake-checkbox to material checkboxes
$((selector) ? selector : this.options.checkboxElements)
.filter(":notmdproc")
.data("mdproc", true)
.after("<span class=checkbox-material><span class=check></span></span>");
},
"togglebutton": function(selector) {
// Add fake-checkbox to material checkboxes
$((selector) ? selector : this.options.togglebuttonElements)
.filter(":notmdproc")
.data("mdproc", true)
.after("<span class=toggle></span>");
},
"radio": function(selector) {
// Add fake-radio to material radios
$((selector) ? selector : this.options.radioElements)
.filter(":notmdproc")
.data("mdproc", true)
.after("<span class=circle></span><span class=check></span>");
},
"input": function(selector) {
$((selector) ? selector : this.options.inputElements)
.filter(":notmdproc")
.data("mdproc", true)
.each( function() {
var $this = $(this);
if (!$(this).attr("data-hint") && !$this.hasClass("floating-label")) {
return;
}
$this.wrap("<div class=form-control-wrapper></div>");
$this.after("<span class=material-input></span>");
// Add floating label if required
if ($this.hasClass("floating-label")) {
var placeholder = $this.attr("placeholder");
$this.attr("placeholder", null).removeClass("floating-label");
$this.after("<div class=floating-label>" + placeholder + "</div>");
}
// Add hint label if required
if ($this.attr("data-hint")) {
$this.after("<div class=hint>" + $this.attr("data-hint") + "</div>");
}
// Set as empty if is empty (damn I must improve this...)
if ($this.val() === null || $this.val() == "undefined" || $this.val() === "") {
$this.addClass("empty");
}
// Support for file input
if ($this.parent().next().is("[type=file]")) {
$this.parent().addClass("fileinput");
var $input = $this.parent().next().detach();
$this.after($input);
}
});
$(document)
.on("change", ".checkbox input[type=checkbox]", function() { $(this).blur(); })
.on("keydown paste", ".form-control", function(e) {
if(_isChar(e)) {
$(this).removeClass("empty");
}
})
.on("keyup change", ".form-control", function() {
var $this = $(this);
if ($this.val() === "" && (typeof $this[0].checkValidity != "undefined" && $this[0].checkValidity())) {
$this.addClass("empty");
} else {
$this.removeClass("empty");
}
})
.on("focus", ".form-control-wrapper.fileinput", function() {
$(this).find("input").addClass("focus");
})
.on("blur", ".form-control-wrapper.fileinput", function() {
$(this).find("input").removeClass("focus");
})
.on("change", ".form-control-wrapper.fileinput [type=file]", function() {
var value = "";
$.each($(this)[0].files, function(i, file) {
value += file.name + ", ";
});
value = value.substring(0, value.length - 2);
if (value) {
$(this).prev().removeClass("empty");
} else {
$(this).prev().addClass("empty");
}
$(this).prev().val(value);
});
},
"ripples": function(selector) {
$((selector) ? selector : this.options.withRipples).ripples();
},
"autofill": function() {
// This part of code will detect autofill when the page is loading (username and password inputs for example)
var loading = setInterval(function() {
$("input[type!=checkbox]").each(function() {
if ($(this).val() && $(this).val() !== $(this).attr("value")) {
$(this).trigger("change");
}
});
}, 100);
// After 10 seconds we are quite sure all the needed inputs are autofilled then we can stop checking them
setTimeout(function() {
clearInterval(loading);
}, 10000);
// Now we just listen on inputs of the focused form (because user can select from the autofill dropdown only when the input has focus)
var focused;
$(document)
.on("focus", "input", function() {
var $inputs = $(this).parents("form").find("input").not("[type=file]");
focused = setInterval(function() {
$inputs.each(function() {
if ($(this).val() !== $(this).attr("value")) {
$(this).trigger("change");
}
});
}, 100);
})
.on("blur", "input", function() {
clearInterval(focused);
});
},
"init": function() {
if ($.fn.ripples && this.options.ripples) {
console.log('ripples');
this.ripples();
}
if (this.options.input) {
console.log('input');
this.input();
}
if (this.options.checkbox) {
console.log('checkbox');
this.checkbox();
}
if (this.options.togglebutton) {
console.log('togglebutton');
this.togglebutton();
}
if (this.options.radio) {
console.log('radio');
this.radio();
}
if (this.options.autofill) {
console.log('autofill');
this.autofill();
}
if (document.arrive && this.options.arrive) {
if ($.fn.ripples && this.options.ripples) {
$(document).arrive(this.options.withRipples, function() {
$.material.ripples($(this));
});
}
if (this.options.input) {
$(document).arrive(this.options.inputElements, function() {
$.material.input($(this));
});
}
if (this.options.checkbox) {
$(document).arrive(this.options.checkboxElements, function() {
$.material.checkbox($(this));
});
}
if (this.options.radio) {
$(document).arrive(this.options.radioElements, function() {
$.material.radio($(this));
});
}
if (this.options.togglebutton) {
$(document).arrive(this.options.togglebuttonElements, function() {
$.material.togglebutton($(this));
});
}
}
}
};
})(jQuery);
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
'use strict';
var storageService = angular.module('storageService', ['ngStorage']);
storageService.service('storage', ['$rootScope', '$localStorage', 'verto', function($rootScope, $localStorage, verto) {
var data = $localStorage;
data.$default({
ui_connected: false,
ws_connected: false,
cur_call: 0,
called_number: '',
useVideo: true,
call_history: [],
call_start: false,
name: '',
email: '',
login: '',
password: '',
userStatus: 'disconnected',
mutedVideo: false,
mutedMic: false,
verto: verto
});
return {
data: data,
reset: function() {
data.ui_connected = false;
data.ws_connected = false;
data.cur_call = 0;
data.userStatus = 'disconnected';
},
};
}]);
<div>
<div class="modal-header">
<button type="button" class="close pull-right" ng-click="$dismiss()" aria-hidden="true">×</button>
<h4 class="modal-title">{{options.title}}</h4>
</div>
<div class="modal-body">
<p ng-if="options.message">
{{options.message}}
</p>
<form id="cgPromptForm" name="cgPromptForm" ng-if="options.input" ng-submit="submit()">
<div class="form-group" ng-class="{'has-error':cgPromptForm.$invalid && changed}">
<label for="cgPromptInput">{{options.label}}</label>
<input id="cgPromptInput" type="text" class="form-control" placeholder="{{options.label}}" ng-model="input.name" required ng-change="changed=true" ng-if="!options.values || options.values.length === 0"/ autofocus="autofocus">
<div class="input-group" ng-if="options.values">
<input id="cgPromptInput" type="text" class="form-control" placeholder="{{options.label}}" ng-model="input.name" required ng-change="changed=true" autofocus="autofocus"/>
<div class="input-group-btn" dropdown>
<button type="button" class="btn btn-default dropdown-toggle" dropdown-toggle data-toggle="dropdown"><span class="caret"></span></button>
<ul class="dropdown-menu pull-right">
<li ng-repeat="value in options.values"><a href="" ng-click="input.name = value">{{value}}</a></li>
</ul>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button ng-repeat="button in options.buttons track by button.label" class="btn btn-default {{button.class}}" ng-class="{'btn-primary':button.primary}" ng-click="buttonClicked(button)">{{button.label}}</button>
</div>
</div>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论