Browse Source

Merge branch 'release-0.23'

pull/175/head v0.23.4
Simon Eisenmann 11 years ago
parent
commit
40a7711e07
  1. 1
      .hound.yml
  2. 1
      .javascript_ignore
  3. 14
      README.md
  4. 29
      build/r.js
  5. 16
      debian/changelog
  6. 9
      html/main.html
  7. 10
      server.conf.in
  8. 0
      src/app/spreed-webrtc-server/channelling.go
  9. 89
      src/app/spreed-webrtc-server/channelling_api.go
  10. 20
      src/app/spreed-webrtc-server/channelling_api_test.go
  11. 6
      src/app/spreed-webrtc-server/client.go
  12. 18
      src/app/spreed-webrtc-server/config.go
  13. 12
      src/app/spreed-webrtc-server/connection.go
  14. 22
      src/app/spreed-webrtc-server/hub.go
  15. 4
      src/app/spreed-webrtc-server/main.go
  16. 61
      src/app/spreed-webrtc-server/room_manager.go
  17. 25
      src/app/spreed-webrtc-server/room_manager_test.go
  18. 18
      src/app/spreed-webrtc-server/roomworker.go
  19. 208
      src/app/spreed-webrtc-server/session.go
  20. 34
      src/app/spreed-webrtc-server/session_manager.go
  21. 4
      src/app/spreed-webrtc-server/user.go
  22. 2
      src/app/spreed-webrtc-server/ws.go
  23. 61
      src/i18n/messages-de.po
  24. 59
      src/i18n/messages-ja.po
  25. 59
      src/i18n/messages-ko.po
  26. 59
      src/i18n/messages-zh-cn.po
  27. 59
      src/i18n/messages-zh-tw.po
  28. 61
      src/i18n/messages.pot
  29. 13
      src/styles/components/_buddylist.scss
  30. 2
      static/css/main.min.css
  31. 34
      static/js/controllers/mediastreamcontroller.js
  32. 3
      static/js/directives/audiovideo.js
  33. 6
      static/js/directives/directives.js
  34. 46
      static/js/directives/menu.js
  35. 14
      static/js/directives/roombar.js
  36. 1
      static/js/directives/welcome.js
  37. 135
      static/js/libs/lodash.min.js
  38. 2112
      static/js/libs/require/require.js
  39. 21
      static/js/mediastream/api.js
  40. 7
      static/js/mediastream/webrtc.js
  41. 7
      static/js/services/appdata.js
  42. 6
      static/js/services/buddylist.js
  43. 14
      static/js/services/rooms.js
  44. 10
      static/partials/menu.html
  45. 10
      static/partials/page/welcome.html
  46. 2
      static/translation/messages-de.json
  47. 2
      static/translation/messages-ja.json
  48. 2
      static/translation/messages-ko.json
  49. 2
      static/translation/messages-zh-cn.json
  50. 2
      static/translation/messages-zh-tw.json

1
.hound.yml

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
java_script:
enabled: true
config_file: .jshint
ignore_file: .javascript_ignore

1
.javascript_ignore

@ -0,0 +1 @@ @@ -0,0 +1 @@
static/js/libs/**.js

14
README.md

@ -108,20 +108,6 @@ The latest version of Spreed WebRTC can be found on GitHub: @@ -108,20 +108,6 @@ The latest version of Spreed WebRTC can be found on GitHub:
and CSS reload directly.
## Branding
Insert logo in `static/img`. Edit `src/styles/global/_branding.scss` to link
to desired custom logo. It is also possible to insert the raw svg data.
## Skins
Insert skins in `src/styles/global/skins` and edit the `@import "skins/light";`
line in `src/styles/global/_variables.scss`. Available skins are light and
dark. It is recommended to create a new skin file if you wish to customize
colors.
## Running for production
Spreed WebRTC should be run through a SSL frontend proxy with

29
build/r.js

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/**
* @license r.js 2.1.14 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
* @license r.js 2.1.15 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/requirejs for details
*/
@ -20,7 +20,7 @@ var requirejs, require, define, xpcUtil; @@ -20,7 +20,7 @@ var requirejs, require, define, xpcUtil;
(function (console, args, readFileFunc) {
var fileName, env, fs, vm, path, exec, rhinoContext, dir, nodeRequire,
nodeDefine, exists, reqMain, loadedOptimizedLib, existsForNode, Cc, Ci,
version = '2.1.14',
version = '2.1.15',
jsSuffixRegExp = /\.js$/,
commandOption = '',
useLibLoaded = {},
@ -238,7 +238,7 @@ var requirejs, require, define, xpcUtil; @@ -238,7 +238,7 @@ var requirejs, require, define, xpcUtil;
}
/** vim: et:ts=4:sw=4:sts=4
* @license RequireJS 2.1.14 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
* @license RequireJS 2.1.15 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/requirejs for details
*/
@ -251,7 +251,7 @@ var requirejs, require, define, xpcUtil; @@ -251,7 +251,7 @@ var requirejs, require, define, xpcUtil;
(function (global) {
var req, s, head, baseElement, dataMain, src,
interactiveScript, currentlyAddingScript, mainScript, subPath,
version = '2.1.14',
version = '2.1.15',
commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
jsSuffixRegExp = /\.js$/,
@ -22949,7 +22949,7 @@ define('parse', ['./esprimaAdapter', 'lang'], function (esprima, lang) { @@ -22949,7 +22949,7 @@ define('parse', ['./esprimaAdapter', 'lang'], function (esprima, lang) {
//Like traverse, but skips if branches that would not be processed
//after has application that results in tests of true or false boolean
//literal values.
var key, child, result, i, params, param,
var key, child, result, i, params, param, tempObject,
hasHas = options && options.has;
fnExpScope = fnExpScope || emptyScope;
@ -22979,14 +22979,14 @@ define('parse', ['./esprimaAdapter', 'lang'], function (esprima, lang) { @@ -22979,14 +22979,14 @@ define('parse', ['./esprimaAdapter', 'lang'], function (esprima, lang) {
//Build up a "scope" object that informs nested recurse calls if
//the define call references an identifier that is likely a UMD
//wrapped function expresion argument.
//wrapped function expression argument.
if (object.type === 'ExpressionStatement' && object.expression &&
object.expression.type === 'CallExpression' && object.expression.callee &&
object.expression.callee.type === 'FunctionExpression') {
object = object.expression.callee;
tempObject = object.expression.callee;
if (object.params && object.params.length) {
params = object.params;
if (tempObject.params && tempObject.params.length) {
params = tempObject.params;
fnExpScope = mixin({}, fnExpScope, true);
for (i = 0; i < params.length; i++) {
param = params[i];
@ -23014,12 +23014,12 @@ define('parse', ['./esprimaAdapter', 'lang'], function (esprima, lang) { @@ -23014,12 +23014,12 @@ define('parse', ['./esprimaAdapter', 'lang'], function (esprima, lang) {
//wrapping.
if (typeof result === 'string') {
if (hasProp(fnExpScope, result)) {
//Just a plain return, parsing can continue past this
//point.
return;
//result still in scope, keep jumping out indicating the
//identifier still in use.
return result;
}
return result;
return;
}
}
};
@ -26539,7 +26539,8 @@ define('build', function (require) { @@ -26539,7 +26539,8 @@ define('build', function (require) {
"excludeShallow": true,
"insertRequire": true,
"stubModules": true,
"deps": true
"deps": true,
"mainConfigFile": true
};
for (i = 0; i < ary.length; i++) {

16
debian/changelog vendored

@ -1,3 +1,19 @@ @@ -1,3 +1,19 @@
spreed-webrtc-server (0.23.4) precise; urgency=low
* Cleanup of README.
* Fixed a problem where videos were not sized correctly.
* Lodash was updated to 3.0.0.
* Server now has an option to require authentication to join rooms.
* Screen sharing, youtube and presentation modules can now be disabled in server.conf.
* Fixed position of buddy list loader animation.
* Fixed loaded of anonymous user data when a plugin uses the authorization api.
* Refactored session cleanup to fix ghost sessions.
* Reorganization to allow better app support.
* Updated require.js and r.js to 2.1.5.
* Fixed room reset when default room is disabled.
-- Simon Eisenmann <simon@struktur.de> Mon, 02 Feb 2015 17:18:53 +0100
spreed-webrtc-server (0.23.3) precise; urgency=low
* Improved room bar room change and leave buttons.

9
html/main.html

@ -22,14 +22,7 @@ @@ -22,14 +22,7 @@
<div class="navbar-collapse collapse" collapse="isCollapsed">
<ul class="nav navbar-nav navbar-right right">
<li class="ng-cloak">
<button title="{{_('Share a YouTube video')}}" class="btn aenablebtn btn-youtubevideo" ng-show="status=='connected' || status=='conference' || layout.youtubevideo" ng-model="layout.youtubevideo" btn-checkbox><i class="fa fa-youtube"></i></button>
<button title="{{_('Share a file as presentation')}}" class="btn aenablebtn btn-presentation" ng-show="status=='connected' || status=='conference' || layout.presentation" ng-model="layout.presentation" btn-checkbox><i class="fa fa-folder-open-o"></i></button>
<button title="{{_('Share your screen')}}" class="btn aenablebtn btn-screenshare" ng-disabled="!supported.screensharing" ng-show="status=='connected' || status=='conference' || layout.screenshare" ng-model="layout.screenshare" btn-checkbox><i class="fa fa-desktop"></i></button>
<button title="{{_('Chat')}}" class="btn btn-chat" ng-class="{messagesunseen: chatMessagesUnseen>0}" ng-model="layout.chat" btn-checkbox btn-checkbox-true="true" btn-checkbox-false="false"><i class="fa fa-comments-o"></i><span class="badge" ng-show="chatMessagesUnseen" ng-bind="chatMessagesUnseen"></span></button>
<button ng-show="myid && myuserid" title="{{_('Contacts')}}" class="btn btn-contacts" ng-click="openContactsManager()"><i class="fa fa-sitemap"></i></button>
<button title="{{_('Mute microphone')}}" class="btn btn-mutemicrophone amutebtn" ng-model="microphoneMute" btn-checkbox btn-checkbox-true="true" btn-checkbox-false="false"><i class="fa"></i></button>
<button title="{{_('Turn camera off')}}" class="btn btn-mutecamera amutebtn" ng-model="cameraMute" btn-checkbox btn-checkbox-true="true" btn-checkbox-false="false"><i class="fa"></i></button>
<button title="{{_('Settings')}}" class="btn btn-settings" ng-model="layout.settings" btn-checkbox btn-checkbox-true="true" btn-checkbox-false="false"><i class="fa fa-cog"></i></button>
<menu></menu>
</li>
</ul>
</div>

10
server.conf.in

@ -78,6 +78,9 @@ encryptionSecret = tne-default-encryption-block-key @@ -78,6 +78,9 @@ encryptionSecret = tne-default-encryption-block-key
; all users will join this room if enabled. If it is disabled then a room join
; form will be shown instead.
;defaultRoomEnabled = true
; Whether a user account is required to join a room. This only has an effect
; if user accounts are enabled. Optional, defaults to false.
;authorizeRoomJoin = false
; Whether a user account is required to create a room. This only has an effect
; if user accounts are enabled. Optional, defaults to false.
;authorizeRoomCreation = false
@ -115,6 +118,13 @@ serverRealm = local @@ -115,6 +118,13 @@ serverRealm = local
; to test your CSP before putting it into production.
;contentSecurityPolicyReportOnly =
[modules]
; Modules provide optional functionality. Modules are enabled by default and
; can be disabled by setting false to their corresponding configuration.
;screensharing = true
;youtube = true
;presentation = true
[log]
;logfile = /var/log/spreed-webrtc-server.log

0
src/app/spreed-webrtc-server/channeling.go → src/app/spreed-webrtc-server/channelling.go

89
src/app/spreed-webrtc-server/channelling_api.go

@ -23,7 +23,6 @@ package main @@ -23,7 +23,6 @@ package main
import (
"log"
"strings"
"time"
)
@ -34,7 +33,6 @@ const ( @@ -34,7 +33,6 @@ const (
type ChannellingAPI interface {
OnConnect(Client, *Session)
OnIncoming(ResponseSender, *Session, *DataIncoming)
OnDisconnect(*Session)
}
type channellingAPI struct {
@ -46,11 +44,9 @@ type channellingAPI struct { @@ -46,11 +44,9 @@ type channellingAPI struct {
ContactManager
TurnDataCreator
Unicaster
Broadcaster
buddyImages ImageCache
}
func NewChannellingAPI(config *Config, roomStatus RoomStatusManager, sessionEncoder SessionEncoder, sessionManager SessionManager, statsCounter StatsCounter, contactManager ContactManager, turnDataCreator TurnDataCreator, unicaster Unicaster, broadcaster Broadcaster, buddyImages ImageCache) ChannellingAPI {
func NewChannellingAPI(config *Config, roomStatus RoomStatusManager, sessionEncoder SessionEncoder, sessionManager SessionManager, statsCounter StatsCounter, contactManager ContactManager, turnDataCreator TurnDataCreator, unicaster Unicaster) ChannellingAPI {
return &channellingAPI{
config,
roomStatus,
@ -60,8 +56,6 @@ func NewChannellingAPI(config *Config, roomStatus RoomStatusManager, sessionEnco @@ -60,8 +56,6 @@ func NewChannellingAPI(config *Config, roomStatus RoomStatusManager, sessionEnco
contactManager,
turnDataCreator,
unicaster,
broadcaster,
buddyImages,
}
}
@ -77,40 +71,31 @@ func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *D @@ -77,40 +71,31 @@ func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *D
case "Hello":
//log.Println("Hello", msg.Hello, c.Index())
// TODO(longsleep): Filter room id and user agent.
api.UpdateSession(session, &SessionUpdate{Types: []string{"Ua"}, Ua: msg.Hello.Ua})
if session.Hello && session.Roomid != msg.Hello.Id {
api.LeaveRoom(session)
api.Broadcast(session, session.DataSessionLeft("soft"))
}
session.Update(&SessionUpdate{Types: []string{"Ua"}, Ua: msg.Hello.Ua})
room, err := session.JoinRoom(msg.Hello.Id, msg.Hello.Credentials, c)
// NOTE(lcooper): Iid filtered for compatibility's sake.
// Evaluate sending unconditionally when supported by all clients.
if room, err := api.JoinRoom(msg.Hello.Id, msg.Hello.Credentials, session, c); err == nil {
session.Hello = true
session.Roomid = msg.Hello.Id
if msg.Iid != "" {
if err == nil {
c.Reply(msg.Iid, &DataWelcome{
Type: "Welcome",
Room: room,
Users: api.RoomUsers(session),
})
}
api.Broadcast(session, session.DataSessionJoined())
} else {
session.Hello = false
if msg.Iid != "" {
c.Reply(msg.Iid, err)
}
}
case "Offer":
// TODO(longsleep): Validate offer
api.Unicast(session, msg.Offer.To, msg.Offer)
session.Unicast(msg.Offer.To, msg.Offer)
case "Candidate":
// TODO(longsleep): Validate candidate
api.Unicast(session, msg.Candidate.To, msg.Candidate)
session.Unicast(msg.Candidate.To, msg.Candidate)
case "Answer":
// TODO(longsleep): Validate Answer
api.Unicast(session, msg.Answer.To, msg.Answer)
session.Unicast(msg.Answer.To, msg.Answer)
case "Users":
if session.Hello {
sessions := &DataSessions{Type: "Users", Users: api.RoomUsers(session)}
@ -125,27 +110,27 @@ func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *D @@ -125,27 +110,27 @@ func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *D
if err := api.Authenticate(session, st, ""); err == nil {
log.Println("Authentication success", session.Userid)
api.SendSelf(c, session)
api.BroadcastSessionStatus(session)
session.BroadcastStatus()
} else {
log.Println("Authentication failed", err, st.Userid, st.Nonce)
}
case "Bye":
api.Unicast(session, msg.Bye.To, msg.Bye)
session.Unicast(msg.Bye.To, msg.Bye)
case "Status":
//log.Println("Status", msg.Status)
api.UpdateSession(session, &SessionUpdate{Types: []string{"Status"}, Status: msg.Status.Status})
api.BroadcastSessionStatus(session)
session.Update(&SessionUpdate{Types: []string{"Status"}, Status: msg.Status.Status})
session.BroadcastStatus()
case "Chat":
// TODO(longsleep): Limit sent chat messages per incoming connection.
if !msg.Chat.Chat.NoEcho {
api.Unicast(session, session.Id, msg.Chat)
session.Unicast(session.Id, msg.Chat)
}
msg.Chat.Chat.Time = time.Now().Format(time.RFC3339)
if msg.Chat.To == "" {
// TODO(longsleep): Check if chat broadcast is allowed.
if session.Hello {
api.CountBroadcastChat()
api.Broadcast(session, msg.Chat)
session.Broadcast(msg.Chat)
}
} else {
if msg.Chat.Chat.Status != nil && msg.Chat.Chat.Status.ContactRequest != nil {
@ -159,10 +144,10 @@ func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *D @@ -159,10 +144,10 @@ func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *D
api.CountUnicastChat()
}
api.Unicast(session, msg.Chat.To, msg.Chat)
session.Unicast(msg.Chat.To, msg.Chat)
if msg.Chat.Chat.Mid != "" {
// Send out delivery confirmation status chat message.
api.Unicast(session, session.Id, &DataChat{To: msg.Chat.To, Type: "Chat", Chat: &DataChatMessage{Mid: msg.Chat.Chat.Mid, Status: &DataChatStatus{State: "sent"}}})
session.Unicast(session.Id, &DataChat{To: msg.Chat.To, Type: "Chat", Chat: &DataChatMessage{Mid: msg.Chat.Chat.Mid, Status: &DataChatStatus{State: "sent"}}})
}
}
case "Conference":
@ -173,7 +158,7 @@ func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *D @@ -173,7 +158,7 @@ func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *D
// Send conference update to anyone.
for _, id := range msg.Conference.Conference {
if id != session.Id {
api.Unicast(session, id, msg.Conference)
session.Unicast(id, msg.Conference)
}
}
}
@ -211,7 +196,7 @@ func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *D @@ -211,7 +196,7 @@ func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *D
}
case "Room":
if room, err := api.UpdateRoom(session, msg.Room); err == nil {
api.Broadcast(session, room)
session.Broadcast(room)
c.Reply(msg.Iid, room)
} else {
c.Reply(msg.Iid, err)
@ -221,23 +206,6 @@ func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *D @@ -221,23 +206,6 @@ func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *D
}
}
func (api *channellingAPI) OnDisconnect(session *Session) {
dsl := session.DataSessionLeft("hard")
if session.Hello {
api.LeaveRoom(session)
api.Broadcast(session, dsl)
}
session.RunForAllSubscribers(func(session *Session) {
log.Println("Notifying subscriber that we are gone", session.Id, session.Id)
api.Unicast(session, session.Id, dsl)
})
api.Unicaster.OnDisconnect(session)
api.buddyImages.Delete(session.Id)
}
func (api *channellingAPI) SendSelf(c Responder, session *Session) {
token, err := api.EncodeSessionToken(session)
if err != nil {
@ -259,26 +227,3 @@ func (api *channellingAPI) SendSelf(c Responder, session *Session) { @@ -259,26 +227,3 @@ func (api *channellingAPI) SendSelf(c Responder, session *Session) {
}
c.Reply("", self)
}
func (api *channellingAPI) UpdateSession(session *Session, s *SessionUpdate) uint64 {
if s.Status != nil {
status, ok := s.Status.(map[string]interface{})
if ok && status["buddyPicture"] != nil {
pic := status["buddyPicture"].(string)
if strings.HasPrefix(pic, "data:") {
imageId := api.buddyImages.Update(session.Id, pic[5:])
if imageId != "" {
status["buddyPicture"] = "img:" + imageId
}
}
}
}
return session.Update(s)
}
func (api *channellingAPI) BroadcastSessionStatus(session *Session) {
if session.Hello {
api.Broadcast(session, session.DataSessionStatus())
}
}

20
src/app/spreed-webrtc-server/channelling_api_test.go

@ -57,17 +57,17 @@ func (fake *fakeRoomManager) RoomUsers(session *Session) []*DataSession { @@ -57,17 +57,17 @@ func (fake *fakeRoomManager) RoomUsers(session *Session) []*DataSession {
return fake.roomUsers
}
func (fake *fakeRoomManager) JoinRoom(id string, _ *DataRoomCredentials, session *Session, _ Sender) (*DataRoom, error) {
func (fake *fakeRoomManager) JoinRoom(id string, _ *DataRoomCredentials, session *Session, sessionAuthenticated bool, _ Sender) (*DataRoom, error) {
fake.joinedID = id
return &DataRoom{Name: id}, fake.joinError
}
func (fake *fakeRoomManager) LeaveRoom(session *Session) {
fake.leftID = session.Roomid
func (fake *fakeRoomManager) LeaveRoom(roomID, sessionID string) {
fake.leftID = roomID
}
func (fake *fakeRoomManager) Broadcast(_ *Session, msg interface{}) {
fake.broadcasts = append(fake.broadcasts, msg)
func (fake *fakeRoomManager) Broadcast(_, _ string, outgoing *DataOutgoing) {
fake.broadcasts = append(fake.broadcasts, outgoing.Data)
}
func (fake *fakeRoomManager) UpdateRoom(_ *Session, _ *DataRoom) (*DataRoom, error) {
@ -98,8 +98,14 @@ func assertErrorReply(t *testing.T, client *fakeClient, iid, code string) { @@ -98,8 +98,14 @@ func assertErrorReply(t *testing.T, client *fakeClient, iid, code string) {
}
func NewTestChannellingAPI() (ChannellingAPI, *fakeClient, *Session, *fakeRoomManager) {
client, roomManager, session := &fakeClient{}, &fakeRoomManager{}, &Session{}
return NewChannellingAPI(nil, roomManager, nil, nil, nil, nil, nil, nil, roomManager, nil), client, session, roomManager
client, roomManager := &fakeClient{}, &fakeRoomManager{}
session := &Session{
attestations: sessionNonces,
Broadcaster: roomManager,
RoomStatusManager: roomManager,
}
session.attestation = &SessionAttestation{s: session}
return NewChannellingAPI(nil, roomManager, nil, nil, nil, nil, nil, nil), client, session, roomManager
}
func Test_ChannellingAPI_OnIncoming_HelloMessage_JoinsTheSelectedRoom(t *testing.T) {

6
src/app/spreed-webrtc-server/client.go

@ -42,7 +42,7 @@ type Client interface { @@ -42,7 +42,7 @@ type Client interface {
ResponseSender
Session() *Session
Index() uint64
Close(bool)
Close()
}
type client struct {
@ -70,10 +70,6 @@ func (client *client) OnText(b Buffer) { @@ -70,10 +70,6 @@ func (client *client) OnText(b Buffer) {
}
}
func (client *client) OnDisconnect() {
client.ChannellingAPI.OnDisconnect(client.session)
}
func (client *client) Reply(iid string, m interface{}) {
outgoing := &DataOutgoing{From: client.session.Id, Iid: iid, Data: m}
if b, err := client.EncodeOutgoing(outgoing); err == nil {

18
src/app/spreed-webrtc-server/config.go

@ -23,11 +23,11 @@ package main @@ -23,11 +23,11 @@ package main
import (
"fmt"
"github.com/strukturag/phoenix"
"log"
"net/http"
"strings"
"time"
"github.com/strukturag/phoenix"
)
type Config struct {
@ -46,6 +46,8 @@ type Config struct { @@ -46,6 +46,8 @@ type Config struct {
DefaultRoomEnabled bool // Flag if default room ("") is enabled
Plugin string // Plugin to load
AuthorizeRoomCreation bool // Whether a user account is required to create rooms
AuthorizeRoomJoin bool // Whether a user account is required to join rooms
Modules []string // List of enabled modules
globalRoomID string // Id of the global room (not exported to Javascript)
contentSecurityPolicy string // HTML content security policy
contentSecurityPolicyReportOnly string // HTML content security policy in report only mode
@ -85,6 +87,16 @@ func NewConfig(container phoenix.Container, tokens bool) *Config { @@ -85,6 +87,16 @@ func NewConfig(container phoenix.Container, tokens bool) *Config {
turnURIs := strings.Split(turnURIsString, " ")
trimAndRemoveDuplicates(&turnURIs)
// Get enabled modules.
allModules := []string{"screensharing", "youtube", "presentation"}
modules := allModules[:0]
for _, module := range allModules {
if container.GetBoolDefault("modules", module, true) {
modules = append(modules, module)
}
}
log.Println("Enabled modules:", modules)
return &Config{
Title: container.GetStringDefault("app", "title", "Spreed WebRTC"),
ver: ver,
@ -101,6 +113,8 @@ func NewConfig(container phoenix.Container, tokens bool) *Config { @@ -101,6 +113,8 @@ func NewConfig(container phoenix.Container, tokens bool) *Config {
DefaultRoomEnabled: container.GetBoolDefault("app", "defaultRoomEnabled", true),
Plugin: container.GetStringDefault("app", "plugin", ""),
AuthorizeRoomCreation: container.GetBoolDefault("app", "authorizeRoomCreation", false),
AuthorizeRoomJoin: container.GetBoolDefault("app", "authorizeRoomJoin", false),
Modules: modules,
globalRoomID: container.GetStringDefault("app", "globalRoom", ""),
contentSecurityPolicy: container.GetStringDefault("app", "contentSecurityPolicy", ""),
contentSecurityPolicyReportOnly: container.GetStringDefault("app", "contentSecurityPolicyReportOnly", ""),

12
src/app/spreed-webrtc-server/connection.go

@ -56,7 +56,7 @@ const ( @@ -56,7 +56,7 @@ const (
type Connection interface {
Index() uint64
Send(Buffer)
Close(runCallbacks bool)
Close()
readPump()
writePump()
}
@ -65,7 +65,6 @@ type ConnectionHandler interface { @@ -65,7 +65,6 @@ type ConnectionHandler interface {
NewBuffer() Buffer
OnConnect(Connection)
OnText(Buffer)
OnDisconnect()
}
type connection struct {
@ -98,15 +97,12 @@ func (c *connection) Index() uint64 { @@ -98,15 +97,12 @@ func (c *connection) Index() uint64 {
return c.Idx
}
func (c *connection) Close(runCallbacks bool) {
func (c *connection) Close() {
c.mutex.Lock()
if c.isClosed {
c.mutex.Unlock()
return
}
if runCallbacks {
c.handler.OnDisconnect()
}
c.ws.Close()
c.isClosed = true
for {
@ -170,7 +166,7 @@ func (c *connection) readPump() { @@ -170,7 +166,7 @@ func (c *connection) readPump() {
}
}
c.Close(true)
c.Close()
}
// Write message to outbound queue.
@ -269,7 +265,7 @@ func (c *connection) writePump() { @@ -269,7 +265,7 @@ func (c *connection) writePump() {
cleanup:
//fmt.Println("writePump done")
timer.Stop()
c.Close(true)
c.Close()
}
// Write ping message.

22
src/app/spreed-webrtc-server/hub.go

@ -48,8 +48,8 @@ type SessionStore interface { @@ -48,8 +48,8 @@ type SessionStore interface {
type Unicaster interface {
SessionStore
OnConnect(Client, *Session)
Unicast(session *Session, to string, m interface{})
OnDisconnect(*Session)
Unicast(to string, outgoing *DataOutgoing)
OnDisconnect(sessionID string)
}
type ContactManager interface {
@ -158,7 +158,7 @@ func (h *hub) OnConnect(client Client, session *Session) { @@ -158,7 +158,7 @@ func (h *hub) OnConnect(client Client, session *Session) {
// Register connection or replace existing one.
if ec, ok := h.clients[session.Id]; ok {
ec.Close(false)
ec.Close()
//log.Printf("Register (%d) from %s: %s (existing)\n", c.Idx, c.Id)
}
h.clients[session.Id] = client
@ -167,26 +167,20 @@ func (h *hub) OnConnect(client Client, session *Session) { @@ -167,26 +167,20 @@ func (h *hub) OnConnect(client Client, session *Session) {
//log.Printf("Register (%d) from %s: %s\n", c.Idx, c.Id)
}
func (h *hub) OnDisconnect(session *Session) {
func (h *hub) OnDisconnect(sessionID string) {
h.mutex.Lock()
delete(h.clients, session.Id)
delete(h.clients, sessionID)
h.mutex.Unlock()
}
func (h *hub) GetClient(id string) (client Client, ok bool) {
func (h *hub) GetClient(sessionID string) (client Client, ok bool) {
h.mutex.RLock()
client, ok = h.clients[id]
client, ok = h.clients[sessionID]
h.mutex.RUnlock()
return
}
func (h *hub) Unicast(session *Session, to string, m interface{}) {
outgoing := &DataOutgoing{
From: session.Id,
To: to,
A: session.Attestation(),
Data: m,
}
func (h *hub) Unicast(to string, outgoing *DataOutgoing) {
if message, err := h.EncodeOutgoing(outgoing); err == nil {
client, ok := h.GetClient(to)
if !ok {

4
src/app/spreed-webrtc-server/main.go

@ -340,9 +340,9 @@ func runner(runtime phoenix.Runtime) error { @@ -340,9 +340,9 @@ func runner(runtime phoenix.Runtime) error {
roomManager := NewRoomManager(config, codec)
hub := NewHub(config, sessionSecret, encryptionSecret, turnSecret, codec)
tickets := NewTickets(sessionSecret, encryptionSecret, computedRealm)
sessionManager := NewSessionManager(config, tickets, sessionSecret)
sessionManager := NewSessionManager(config, tickets, hub, roomManager, roomManager, buddyImages, sessionSecret)
statsManager := NewStatsManager(hub, roomManager, sessionManager)
channellingAPI := NewChannellingAPI(config, roomManager, tickets, sessionManager, statsManager, hub, hub, hub, roomManager, buddyImages)
channellingAPI := NewChannellingAPI(config, roomManager, tickets, sessionManager, statsManager, hub, hub, hub)
r.HandleFunc("/", httputils.MakeGzipHandler(mainHandler))
r.Handle("/static/img/buddy/{flags}/{imageid}/{idx:.*}", http.StripPrefix(config.B, makeImageHandler(buddyImages, time.Duration(24)*time.Hour)))
r.Handle("/static/{path:.*}", http.StripPrefix(config.B, httputils.FileStaticServer(http.Dir(rootFolder))))

61
src/app/spreed-webrtc-server/room_manager.go

@ -28,13 +28,13 @@ import ( @@ -28,13 +28,13 @@ import (
type RoomStatusManager interface {
RoomUsers(*Session) []*DataSession
JoinRoom(string, *DataRoomCredentials, *Session, Sender) (*DataRoom, error)
LeaveRoom(*Session)
JoinRoom(roomID string, credentials *DataRoomCredentials, session *Session, sessionAuthenticated bool, sender Sender) (*DataRoom, error)
LeaveRoom(roomID, sessionID string)
UpdateRoom(*Session, *DataRoom) (*DataRoom, error)
}
type Broadcaster interface {
Broadcast(*Session, interface{})
Broadcast(sessionID, roomID string, outgoing *DataOutgoing)
}
type RoomStats interface {
@ -71,12 +71,12 @@ func (rooms *roomManager) RoomUsers(session *Session) []*DataSession { @@ -71,12 +71,12 @@ func (rooms *roomManager) RoomUsers(session *Session) []*DataSession {
return []*DataSession{}
}
func (rooms *roomManager) JoinRoom(id string, credentials *DataRoomCredentials, session *Session, sender Sender) (*DataRoom, error) {
if id == "" && !rooms.DefaultRoomEnabled {
func (rooms *roomManager) JoinRoom(roomID string, credentials *DataRoomCredentials, session *Session, sessionAuthenticated bool, sender Sender) (*DataRoom, error) {
if roomID == "" && !rooms.DefaultRoomEnabled {
return nil, NewDataError("default_room_disabled", "The default room is not enabled")
}
roomWorker, err := rooms.GetOrCreate(id, credentials, session)
roomWorker, err := rooms.GetOrCreate(roomID, credentials, sessionAuthenticated)
if err != nil {
return nil, err
}
@ -84,9 +84,9 @@ func (rooms *roomManager) JoinRoom(id string, credentials *DataRoomCredentials, @@ -84,9 +84,9 @@ func (rooms *roomManager) JoinRoom(id string, credentials *DataRoomCredentials,
return roomWorker.Join(credentials, session, sender)
}
func (rooms *roomManager) LeaveRoom(session *Session) {
if room, ok := rooms.Get(session.Roomid); ok {
room.Leave(session)
func (rooms *roomManager) LeaveRoom(roomID, sessionID string) {
if room, ok := rooms.Get(roomID); ok {
room.Leave(sessionID)
}
}
@ -104,29 +104,22 @@ func (rooms *roomManager) UpdateRoom(session *Session, room *DataRoom) (*DataRoo @@ -104,29 +104,22 @@ func (rooms *roomManager) UpdateRoom(session *Session, room *DataRoom) (*DataRoo
return room, nil
}
func (rooms *roomManager) Broadcast(session *Session, m interface{}) {
outgoing := &DataOutgoing{
From: session.Id,
A: session.Attestation(),
Data: m,
}
func (rooms *roomManager) Broadcast(sessionID, roomID string, outgoing *DataOutgoing) {
message, err := rooms.EncodeOutgoing(outgoing)
if err != nil {
return
}
id := session.Roomid
if id != "" && id == rooms.globalRoomID {
if roomID != "" && roomID == rooms.globalRoomID {
rooms.RLock()
for _, room := range rooms.roomTable {
room.Broadcast(session, message)
room.Broadcast(sessionID, message)
}
rooms.RUnlock()
} else if room, ok := rooms.Get(id); ok {
room.Broadcast(session, message)
} else if room, ok := rooms.Get(roomID); ok {
room.Broadcast(sessionID, message)
} else {
log.Printf("No room named %s found for broadcast message %#v", id, m)
log.Printf("No room named %s found for broadcast %#v", roomID, outgoing)
}
message.Decref()
}
@ -145,33 +138,37 @@ func (rooms *roomManager) RoomInfo(includeSessions bool) (count int, sessionInfo @@ -145,33 +138,37 @@ func (rooms *roomManager) RoomInfo(includeSessions bool) (count int, sessionInfo
return
}
func (rooms *roomManager) Get(id string) (room RoomWorker, ok bool) {
func (rooms *roomManager) Get(roomID string) (room RoomWorker, ok bool) {
rooms.RLock()
room, ok = rooms.roomTable[id]
room, ok = rooms.roomTable[roomID]
rooms.RUnlock()
return
}
func (rooms *roomManager) GetOrCreate(id string, credentials *DataRoomCredentials, session *Session) (RoomWorker, error) {
if room, ok := rooms.Get(id); ok {
func (rooms *roomManager) GetOrCreate(roomID string, credentials *DataRoomCredentials, sessionAuthenticated bool) (RoomWorker, error) {
if rooms.AuthorizeRoomJoin && rooms.UsersEnabled && !sessionAuthenticated {
return nil, NewDataError("room_join_requires_account", "Room join requires a user account")
}
if room, ok := rooms.Get(roomID); ok {
return room, nil
}
rooms.Lock()
// Need to re-check, another thread might have created the room
// while we waited for the lock.
if room, ok := rooms.roomTable[id]; ok {
if room, ok := rooms.roomTable[roomID]; ok {
rooms.Unlock()
return room, nil
}
if rooms.UsersEnabled && rooms.AuthorizeRoomCreation && !session.Authenticated() {
if rooms.UsersEnabled && rooms.AuthorizeRoomCreation && !sessionAuthenticated {
rooms.Unlock()
return nil, NewDataError("room_join_requires_account", "Room creation requires a user account")
}
room := NewRoomWorker(rooms, id, credentials)
rooms.roomTable[id] = room
room := NewRoomWorker(rooms, roomID, credentials)
rooms.roomTable[roomID] = room
rooms.Unlock()
go func() {
// Start room, this blocks until room expired.
@ -179,8 +176,8 @@ func (rooms *roomManager) GetOrCreate(id string, credentials *DataRoomCredential @@ -179,8 +176,8 @@ func (rooms *roomManager) GetOrCreate(id string, credentials *DataRoomCredential
// Cleanup room when we are done.
rooms.Lock()
defer rooms.Unlock()
delete(rooms.roomTable, id)
log.Printf("Cleaned up room '%s'\n", id)
delete(rooms.roomTable, roomID)
log.Printf("Cleaned up room '%s'\n", roomID)
}()
return room, nil

25
src/app/spreed-webrtc-server/room_manager_test.go

@ -36,21 +36,40 @@ func Test_RoomManager_JoinRoom_ReturnsAnErrorForUnauthenticatedSessionsWhenCreat @@ -36,21 +36,40 @@ func Test_RoomManager_JoinRoom_ReturnsAnErrorForUnauthenticatedSessionsWhenCreat
config.AuthorizeRoomCreation = true
unauthenticatedSession := &Session{}
_, err := roomManager.JoinRoom("foo", nil, unauthenticatedSession, nil)
_, err := roomManager.JoinRoom("foo", nil, unauthenticatedSession, false, nil)
assertDataError(t, err, "room_join_requires_account")
authenticatedSession := &Session{userid: "9870457"}
_, err = roomManager.JoinRoom("foo", nil, authenticatedSession, nil)
_, err = roomManager.JoinRoom("foo", nil, authenticatedSession, true, nil)
if err != nil {
t.Fatalf("Unexpected error %v joining room while authenticated", err)
}
_, err = roomManager.JoinRoom("foo", nil, unauthenticatedSession, nil)
_, err = roomManager.JoinRoom("foo", nil, unauthenticatedSession, false, nil)
if err != nil {
t.Fatalf("Unexpected error %v joining room while unauthenticated", err)
}
}
func Test_RoomManager_JoinRoom_ReturnsAnErrorForUnauthenticatedSessionsWhenJoinRequiresAnAccount(t *testing.T) {
roomManager, config := NewTestRoomManager()
config.UsersEnabled = true
config.AuthorizeRoomJoin = true
unauthenticatedSession := &Session{}
_, err := roomManager.JoinRoom("foo", nil, unauthenticatedSession, false, nil)
assertDataError(t, err, "room_join_requires_account")
authenticatedSession := &Session{userid: "9870457"}
_, err = roomManager.JoinRoom("foo", nil, authenticatedSession, true, nil)
if err != nil {
t.Fatalf("Unexpected error %v joining room while authenticated", err)
}
_, err = roomManager.JoinRoom("foo", nil, unauthenticatedSession, false, nil)
assertDataError(t, err, "room_join_requires_account")
}
func Test_RoomManager_UpdateRoom_ReturnsAnErrorIfNoRoomHasBeenJoined(t *testing.T) {
roomManager, _ := NewTestRoomManager()
_, err := roomManager.UpdateRoom(&Session{}, nil)

18
src/app/spreed-webrtc-server/roomworker.go

@ -39,9 +39,9 @@ type RoomWorker interface { @@ -39,9 +39,9 @@ type RoomWorker interface {
Users() []*roomUser
Update(*DataRoom) error
GetUsers() []*DataSession
Broadcast(*Session, Buffer)
Broadcast(sessionID string, buf Buffer)
Join(*DataRoomCredentials, *Session, Sender) (*DataRoom, error)
Leave(*Session)
Leave(sessionID string)
}
type roomWorker struct {
@ -213,19 +213,19 @@ func (r *roomWorker) GetUsers() []*DataSession { @@ -213,19 +213,19 @@ func (r *roomWorker) GetUsers() []*DataSession {
return <-out
}
func (r *roomWorker) Broadcast(session *Session, message Buffer) {
func (r *roomWorker) Broadcast(sessionID string, message Buffer) {
worker := func() {
r.mutex.RLock()
defer r.mutex.RUnlock()
for id, user := range r.users {
if id == session.Id {
if id == sessionID {
// Skip broadcast to self.
continue
}
//fmt.Printf("%s\n", m.Message)
user.Send(message)
}
r.mutex.RUnlock()
message.Decref()
}
@ -273,13 +273,13 @@ func (r *roomWorker) Join(credentials *DataRoomCredentials, session *Session, se @@ -273,13 +273,13 @@ func (r *roomWorker) Join(credentials *DataRoomCredentials, session *Session, se
return result.DataRoom, result.error
}
func (r *roomWorker) Leave(session *Session) {
func (r *roomWorker) Leave(sessionID string) {
worker := func() {
r.mutex.Lock()
defer r.mutex.Unlock()
if _, ok := r.users[session.Id]; ok {
delete(r.users, session.Id)
if _, ok := r.users[sessionID]; ok {
delete(r.users, sessionID)
}
r.mutex.Unlock()
}
r.Run(worker)
}

208
src/app/spreed-webrtc-server/session.go

@ -25,6 +25,7 @@ import ( @@ -25,6 +25,7 @@ import (
"errors"
"fmt"
"github.com/gorilla/securecookie"
"strings"
"sync"
"time"
)
@ -32,6 +33,11 @@ import ( @@ -32,6 +33,11 @@ import (
var sessionNonces *securecookie.SecureCookie
type Session struct {
SessionManager
Unicaster
Broadcaster
RoomStatusManager
buddyImages ImageCache
Id string
Sid string
Ua string
@ -49,11 +55,17 @@ type Session struct { @@ -49,11 +55,17 @@ type Session struct {
attestations *securecookie.SecureCookie
subscriptions map[string]*Session
subscribers map[string]*Session
disconnected bool
}
func NewSession(attestations *securecookie.SecureCookie, id, sid string) *Session {
func NewSession(manager SessionManager, unicaster Unicaster, broadcaster Broadcaster, rooms RoomStatusManager, buddyImages ImageCache, attestations *securecookie.SecureCookie, id, sid string) *Session {
session := &Session{
SessionManager: manager,
Unicaster: unicaster,
Broadcaster: broadcaster,
RoomStatusManager: rooms,
buddyImages: buddyImages,
Id: id,
Sid: sid,
Prio: 100,
@ -67,6 +79,11 @@ func NewSession(attestations *securecookie.SecureCookie, id, sid string) *Sessio @@ -67,6 +79,11 @@ func NewSession(attestations *securecookie.SecureCookie, id, sid string) *Sessio
}
func (s *Session) authenticated() (authenticated bool) {
authenticated = s.userid != ""
return
}
func (s *Session) Subscribe(session *Session) {
s.mutex.Lock()
@ -103,37 +120,144 @@ func (s *Session) RemoveSubscriber(id string) { @@ -103,37 +120,144 @@ func (s *Session) RemoveSubscriber(id string) {
s.mutex.Unlock()
}
func (s *Session) RunForAllSubscribers(f func(session *Session)) {
s.mutex.Lock()
for _, session := range s.subscribers {
s.mutex.Unlock()
f(session)
func (s *Session) JoinRoom(roomID string, credentials *DataRoomCredentials, sender Sender) (*DataRoom, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
if s.Hello && s.Roomid != roomID {
s.RoomStatusManager.LeaveRoom(s.Roomid, s.Id)
s.Broadcaster.Broadcast(s.Id, s.Roomid, &DataOutgoing{
From: s.Id,
A: s.attestation.Token(),
Data: &DataSession{
Type: "Left",
Id: s.Id,
Status: "soft",
},
})
}
s.mutex.Unlock()
room, err := s.RoomStatusManager.JoinRoom(roomID, credentials, s, s.authenticated(), sender)
if err == nil {
s.Hello = true
s.Roomid = roomID
s.Broadcaster.Broadcast(s.Id, s.Roomid, &DataOutgoing{
From: s.Id,
A: s.attestation.Token(),
Data: &DataSession{
Type: "Joined",
Id: s.Id,
Userid: s.userid,
Ua: s.Ua,
Prio: s.Prio,
},
})
} else {
s.Hello = false
}
return room, err
}
func (s *Session) Close() {
func (s *Session) Broadcast(m interface{}) {
s.mutex.RLock()
if s.Hello {
s.Broadcaster.Broadcast(s.Id, s.Roomid, &DataOutgoing{
From: s.Id,
A: s.attestation.Token(),
Data: m,
})
}
s.mutex.RUnlock()
}
func (s *Session) BroadcastStatus() {
s.mutex.RLock()
if s.Hello {
s.Broadcaster.Broadcast(s.Id, s.Roomid, &DataOutgoing{
From: s.Id,
A: s.attestation.Token(),
Data: &DataSession{
Type: "Status",
Id: s.Id,
Userid: s.userid,
Status: s.Status,
Rev: s.UpdateRev,
Prio: s.Prio,
},
})
}
s.mutex.RUnlock()
}
func (s *Session) Unicast(to string, m interface{}) {
s.mutex.RLock()
outgoing := &DataOutgoing{
From: s.Id,
To: to,
A: s.attestation.Token(),
Data: m,
}
s.mutex.RUnlock()
s.Unicaster.Unicast(to, outgoing)
}
func (s *Session) Close() {
s.mutex.Lock()
// Remove foreign references.
outgoing := &DataOutgoing{
From: s.Id,
A: s.attestation.Token(),
Data: &DataSession{
Type: "Left",
Id: s.Id,
Status: "hard",
},
}
if s.Hello {
// NOTE(lcooper): If we don't check for Hello here, we could deadlock
// when implicitly creating a room while a user is reconnecting.
s.Broadcaster.Broadcast(s.Id, s.Roomid, outgoing)
s.RoomStatusManager.LeaveRoom(s.Roomid, s.Id)
}
for _, session := range s.subscribers {
s.Unicaster.Unicast(session.Id, outgoing)
}
for _, session := range s.subscriptions {
session.RemoveSubscriber(s.Id)
}
// Remove session cross references.
s.Unicaster.OnDisconnect(s.Id)
s.SessionManager.DestroySession(s.Id, s.userid)
s.buddyImages.Delete(s.Id)
s.subscriptions = make(map[string]*Session)
s.subscribers = make(map[string]*Session)
s.mutex.Unlock()
s.disconnected = true
s.mutex.Unlock()
}
func (s *Session) Update(update *SessionUpdate) uint64 {
s.mutex.Lock()
defer s.mutex.Unlock()
if update.Status != nil {
status, ok := update.Status.(map[string]interface{})
if ok && status["buddyPicture"] != nil {
pic := status["buddyPicture"].(string)
if strings.HasPrefix(pic, "data:") {
imageId := s.buddyImages.Update(s.Id, pic[5:])
if imageId != "" {
status["buddyPicture"] = "img:" + imageId
}
}
}
}
for _, key := range update.Types {
//fmt.Println("type update", key)
@ -173,13 +297,6 @@ func (s *Session) Authorize(realm string, st *SessionToken) (string, error) { @@ -173,13 +297,6 @@ func (s *Session) Authorize(realm string, st *SessionToken) (string, error) {
}
func (s *Session) Authenticated() (authenticated bool) {
s.mutex.Lock()
authenticated = s.userid != ""
s.mutex.Unlock()
return
}
func (s *Session) Authenticate(realm string, st *SessionToken, userid string) error {
s.mutex.Lock()
@ -252,50 +369,6 @@ func (s *Session) SetUseridFake(userid string) { @@ -252,50 +369,6 @@ func (s *Session) SetUseridFake(userid string) {
}
func (s *Session) DataSessionLeft(state string) *DataSession {
s.mutex.RLock()
defer s.mutex.RUnlock()
return &DataSession{
Type: "Left",
Id: s.Id,
Status: state,
}
}
func (s *Session) DataSessionJoined() *DataSession {
s.mutex.RLock()
defer s.mutex.RUnlock()
return &DataSession{
Type: "Joined",
Id: s.Id,
Userid: s.userid,
Ua: s.Ua,
Prio: s.Prio,
}
}
func (s *Session) DataSessionStatus() *DataSession {
s.mutex.RLock()
defer s.mutex.RUnlock()
return &DataSession{
Type: "Status",
Id: s.Id,
Userid: s.userid,
Status: s.Status,
Rev: s.UpdateRev,
Prio: s.Prio,
}
}
func (s *Session) NewAttestation() {
s.attestation = &SessionAttestation{
s: s,
@ -303,13 +376,6 @@ func (s *Session) NewAttestation() { @@ -303,13 +376,6 @@ func (s *Session) NewAttestation() {
s.attestation.Update()
}
func (s *Session) Attestation() (attestation string) {
s.mutex.RLock()
attestation = s.attestation.Token()
s.mutex.RUnlock()
return
}
func (s *Session) UpdateAttestation() {
s.mutex.Lock()
s.attestation.Update()

34
src/app/spreed-webrtc-server/session_manager.go

@ -37,14 +37,18 @@ type SessionManager interface { @@ -37,14 +37,18 @@ type SessionManager interface {
UserStats
RetrieveUsersWith(func(*http.Request) (string, error))
CreateSession(*http.Request) *Session
DestroySession(*Session)
DestroySession(sessionID, userID string)
Authenticate(*Session, *SessionToken, string) error
GetUserSessions(session *Session, id string) []*DataSession
}
type sessionManager struct {
Tickets
sync.RWMutex
Tickets
Unicaster
Broadcaster
RoomStatusManager
buddyImages ImageCache
config *Config
userTable map[string]*User
fakesessionTable map[string]*Session
@ -52,10 +56,14 @@ type sessionManager struct { @@ -52,10 +56,14 @@ type sessionManager struct {
attestations *securecookie.SecureCookie
}
func NewSessionManager(config *Config, tickets Tickets, sessionSecret []byte) SessionManager {
func NewSessionManager(config *Config, tickets Tickets, unicaster Unicaster, broadcaster Broadcaster, rooms RoomStatusManager, buddyImages ImageCache, sessionSecret []byte) SessionManager {
sessionManager := &sessionManager{
tickets,
sync.RWMutex{},
tickets,
unicaster,
broadcaster,
rooms,
buddyImages,
config,
make(map[string]*User),
make(map[string]*Session),
@ -103,7 +111,7 @@ func (sessionManager *sessionManager) CreateSession(request *http.Request) *Sess @@ -103,7 +111,7 @@ func (sessionManager *sessionManager) CreateSession(request *http.Request) *Sess
}
}
session := NewSession(sessionManager.attestations, st.Id, st.Sid)
session := NewSession(sessionManager, sessionManager.Unicaster, sessionManager.Broadcaster, sessionManager.RoomStatusManager, sessionManager.buddyImages, sessionManager.attestations, st.Id, st.Sid)
if userid != "" {
// XXX(lcooper): Should errors be handled here?
@ -113,15 +121,15 @@ func (sessionManager *sessionManager) CreateSession(request *http.Request) *Sess @@ -113,15 +121,15 @@ func (sessionManager *sessionManager) CreateSession(request *http.Request) *Sess
return session
}
func (sessionManager *sessionManager) DestroySession(session *Session) {
session.Close()
func (sessionManager *sessionManager) DestroySession(sessionID, userID string) {
if userID == "" {
return
}
sessionManager.Lock()
if suserid := session.Userid(); suserid != "" {
user, ok := sessionManager.userTable[suserid]
if ok && user.RemoveSession(session) {
delete(sessionManager.userTable, suserid)
}
user, ok := sessionManager.userTable[userID]
if ok && user.RemoveSession(sessionID) {
delete(sessionManager.userTable, userID)
}
sessionManager.Unlock()
}
@ -158,7 +166,7 @@ func (sessionManager *sessionManager) GetUserSessions(session *Session, userid s @@ -158,7 +166,7 @@ func (sessionManager *sessionManager) GetUserSessions(session *Session, userid s
session, ok := sessionManager.fakesessionTable[userid]
if !ok {
st := sessionManager.FakeSessionToken(userid)
session = NewSession(sessionManager.attestations, st.Id, st.Sid)
session = NewSession(sessionManager, sessionManager.Unicaster, sessionManager.Broadcaster, sessionManager.RoomStatusManager, sessionManager.buddyImages, sessionManager.attestations, st.Id, st.Sid)
session.SetUseridFake(st.Userid)
sessionManager.fakesessionTable[userid] = session
}

4
src/app/spreed-webrtc-server/user.go

@ -57,10 +57,10 @@ func (u *User) AddSession(s *Session) bool { @@ -57,10 +57,10 @@ func (u *User) AddSession(s *Session) bool {
}
// Return true if no session left.
func (u *User) RemoveSession(s *Session) bool {
func (u *User) RemoveSession(sessionID string) bool {
last := false
u.mutex.Lock()
delete(u.sessionTable, s.Id)
delete(u.sessionTable, sessionID)
if len(u.sessionTable) == 0 {
log.Println("Last session unregistered for user", u.Id)
last = true

2
src/app/spreed-webrtc-server/ws.go

@ -69,7 +69,7 @@ func makeWSHandler(connectionCounter ConnectionCounter, sessionManager SessionMa @@ -69,7 +69,7 @@ func makeWSHandler(connectionCounter ConnectionCounter, sessionManager SessionMa
// Create a new connection instance.
session := sessionManager.CreateSession(r)
defer sessionManager.DestroySession(session)
defer session.Close()
client := NewClient(codec, channellingAPI, session)
conn := NewConnection(connectionCounter.CountConnection(), ws, client)

61
src/i18n/messages-de.po

@ -8,8 +8,8 @@ msgid "" @@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2014-12-08 16:48+0100\n"
"PO-Revision-Date: 2014-12-08 16:48+0100\n"
"POT-Creation-Date: 2015-01-28 15:06+0100\n"
"PO-Revision-Date: 2015-01-28 15:07+0100\n"
"Last-Translator: Simon Eisenmann <simon@struktur.de>\n"
"Language-Team: struktur AG <opensource@struktur.de>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
@ -18,30 +18,6 @@ msgstr "" @@ -18,30 +18,6 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
msgid "Share a YouTube video"
msgstr "Ein YouTube Video teilen"
msgid "Share a file as presentation"
msgstr "Datei als Präsentation teilen."
msgid "Share your screen"
msgstr "Bildschirm freigeben"
msgid "Chat"
msgstr "Chat"
msgid "Contacts"
msgstr "Kontakte"
msgid "Mute microphone"
msgstr "Mikrofon abschalten"
msgid "Turn camera off"
msgstr "Kamera abschalten"
msgid "Settings"
msgstr "Einstellungen"
msgid "Your audio level"
msgstr "Ihr Audio-Pegel"
@ -197,6 +173,30 @@ msgstr "Nochmal versuchen" @@ -197,6 +173,30 @@ msgstr "Nochmal versuchen"
msgid "Download failed."
msgstr "Fehler beim Download."
msgid "Share a YouTube video"
msgstr "Ein YouTube Video teilen"
msgid "Share a file as presentation"
msgstr "Datei als Präsentation teilen."
msgid "Share your screen"
msgstr "Bildschirm freigeben"
msgid "Chat"
msgstr "Chat"
msgid "Contacts"
msgstr "Kontakte"
msgid "Mute microphone"
msgstr "Mikrofon abschalten"
msgid "Turn camera off"
msgstr "Kamera abschalten"
msgid "Settings"
msgstr "Einstellungen"
msgid "Loading presentation ..."
msgstr "Präsentation wird geladen..."
@ -231,12 +231,12 @@ msgstr "Raum wechseln" @@ -231,12 +231,12 @@ msgstr "Raum wechseln"
msgid "Room"
msgstr "Raum"
msgid "Main"
msgstr "Standard"
msgid "Leave room"
msgstr "Raum verlassen"
msgid "Main"
msgstr "Standard"
msgid "Current room"
msgstr "Raum"
@ -500,6 +500,9 @@ msgstr "" @@ -500,6 +500,9 @@ msgstr ""
msgid "Room history"
msgstr "Raum-Verlauf"
msgid "Please sign in."
msgstr "Bitte melden Sie sich an."
msgid "Videos are shared with everyone in this call."
msgstr "Das Video wird bei allen Gesprächsteilnehmern angezeigt."

59
src/i18n/messages-ja.po

@ -8,7 +8,7 @@ msgid "" @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2014-12-08 16:48+0100\n"
"POT-Creation-Date: 2015-01-28 15:06+0100\n"
"PO-Revision-Date: 2014-04-23 22:25+0100\n"
"Last-Translator: Curt Frisemo <curt.frisemo@spreed.com>\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -18,30 +18,6 @@ msgstr "" @@ -18,30 +18,6 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
msgid "Share a YouTube video"
msgstr ""
msgid "Share a file as presentation"
msgstr ""
msgid "Share your screen"
msgstr "画面を共有する."
msgid "Chat"
msgstr "チャット"
msgid "Contacts"
msgstr ""
msgid "Mute microphone"
msgstr "消音"
msgid "Turn camera off"
msgstr "カメラをオフにする"
msgid "Settings"
msgstr "設定"
msgid "Your audio level"
msgstr "あなたの音量"
@ -199,6 +175,30 @@ msgstr "リトライ" @@ -199,6 +175,30 @@ msgstr "リトライ"
msgid "Download failed."
msgstr "ダウンロード失敗."
msgid "Share a YouTube video"
msgstr ""
msgid "Share a file as presentation"
msgstr ""
msgid "Share your screen"
msgstr "画面を共有する."
msgid "Chat"
msgstr "チャット"
msgid "Contacts"
msgstr ""
msgid "Mute microphone"
msgstr "消音"
msgid "Turn camera off"
msgstr "カメラをオフにする"
msgid "Settings"
msgstr "設定"
msgid "Loading presentation ..."
msgstr ""
@ -231,12 +231,12 @@ msgstr "ルームチェンジ" @@ -231,12 +231,12 @@ msgstr "ルームチェンジ"
msgid "Room"
msgstr "ルーム"
msgid "Main"
msgstr "メイン"
msgid "Leave room"
msgstr "ルームを出る"
msgid "Main"
msgstr "メイン"
msgid "Current room"
msgstr "現在のルーム"
@ -492,6 +492,9 @@ msgstr "" @@ -492,6 +492,9 @@ msgstr ""
msgid "Room history"
msgstr ""
msgid "Please sign in."
msgstr ""
msgid "Videos are shared with everyone in this call."
msgstr ""

59
src/i18n/messages-ko.po

@ -8,7 +8,7 @@ msgid "" @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2014-12-08 16:48+0100\n"
"POT-Creation-Date: 2015-01-28 15:06+0100\n"
"PO-Revision-Date: 2014-04-13 20:30+0900\n"
"Last-Translator: Curt Frisemo <curt.frisemo@spreed.com>\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -18,30 +18,6 @@ msgstr "" @@ -18,30 +18,6 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
msgid "Share a YouTube video"
msgstr ""
msgid "Share a file as presentation"
msgstr ""
msgid "Share your screen"
msgstr "화면 공유하기"
msgid "Chat"
msgstr "대화"
msgid "Contacts"
msgstr ""
msgid "Mute microphone"
msgstr "음성제거"
msgid "Turn camera off"
msgstr "카메라꺼짐"
msgid "Settings"
msgstr "설정"
msgid "Your audio level"
msgstr "음성크기"
@ -199,6 +175,30 @@ msgstr "재시도" @@ -199,6 +175,30 @@ msgstr "재시도"
msgid "Download failed."
msgstr "다운로드실패"
msgid "Share a YouTube video"
msgstr ""
msgid "Share a file as presentation"
msgstr ""
msgid "Share your screen"
msgstr "화면 공유하기"
msgid "Chat"
msgstr "대화"
msgid "Contacts"
msgstr ""
msgid "Mute microphone"
msgstr "음성제거"
msgid "Turn camera off"
msgstr "카메라꺼짐"
msgid "Settings"
msgstr "설정"
msgid "Loading presentation ..."
msgstr ""
@ -231,12 +231,12 @@ msgstr "방 변경" @@ -231,12 +231,12 @@ msgstr "방 변경"
msgid "Room"
msgstr "방"
msgid "Main"
msgstr "메인"
msgid "Leave room"
msgstr "방 이동"
msgid "Main"
msgstr "메인"
msgid "Current room"
msgstr "현재 방"
@ -492,6 +492,9 @@ msgstr "" @@ -492,6 +492,9 @@ msgstr ""
msgid "Room history"
msgstr ""
msgid "Please sign in."
msgstr ""
msgid "Videos are shared with everyone in this call."
msgstr ""

59
src/i18n/messages-zh-cn.po

@ -8,7 +8,7 @@ msgid "" @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2014-12-08 16:48+0100\n"
"POT-Creation-Date: 2015-01-28 15:06+0100\n"
"PO-Revision-Date: 2014-05-21 09:54+0800\n"
"Last-Translator: Michael P.\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -18,30 +18,6 @@ msgstr "" @@ -18,30 +18,6 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
msgid "Share a YouTube video"
msgstr ""
msgid "Share a file as presentation"
msgstr ""
msgid "Share your screen"
msgstr "共享您的屏幕"
msgid "Chat"
msgstr "聊天"
msgid "Contacts"
msgstr ""
msgid "Mute microphone"
msgstr "关闭麦克风"
msgid "Turn camera off"
msgstr "关闭摄像头"
msgid "Settings"
msgstr "系统设置"
msgid "Your audio level"
msgstr "您的通话音量"
@ -199,6 +175,30 @@ msgstr "重试" @@ -199,6 +175,30 @@ msgstr "重试"
msgid "Download failed."
msgstr "下载失败"
msgid "Share a YouTube video"
msgstr ""
msgid "Share a file as presentation"
msgstr ""
msgid "Share your screen"
msgstr "共享您的屏幕"
msgid "Chat"
msgstr "聊天"
msgid "Contacts"
msgstr ""
msgid "Mute microphone"
msgstr "关闭麦克风"
msgid "Turn camera off"
msgstr "关闭摄像头"
msgid "Settings"
msgstr "系统设置"
msgid "Loading presentation ..."
msgstr ""
@ -231,12 +231,12 @@ msgstr "更换房间" @@ -231,12 +231,12 @@ msgstr "更换房间"
msgid "Room"
msgstr "房间"
msgid "Main"
msgstr "主房间"
msgid "Leave room"
msgstr "离开房间"
msgid "Main"
msgstr "主房间"
msgid "Current room"
msgstr "當前房间"
@ -492,6 +492,9 @@ msgstr "" @@ -492,6 +492,9 @@ msgstr ""
msgid "Room history"
msgstr ""
msgid "Please sign in."
msgstr ""
msgid "Videos are shared with everyone in this call."
msgstr ""

59
src/i18n/messages-zh-tw.po

@ -8,7 +8,7 @@ msgid "" @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2014-12-08 16:48+0100\n"
"POT-Creation-Date: 2015-01-28 15:06+0100\n"
"PO-Revision-Date: 2014-05-21 09:55+0800\n"
"Last-Translator: Michael P.\n"
"Language-Team: Curt Frisemo <curt.frisemo@spreed.com>\n"
@ -18,30 +18,6 @@ msgstr "" @@ -18,30 +18,6 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
msgid "Share a YouTube video"
msgstr ""
msgid "Share a file as presentation"
msgstr ""
msgid "Share your screen"
msgstr "共享您的屏幕"
msgid "Chat"
msgstr "聊天"
msgid "Contacts"
msgstr ""
msgid "Mute microphone"
msgstr "關閉麥克風"
msgid "Turn camera off"
msgstr "關閉攝像頭"
msgid "Settings"
msgstr "系統設置"
msgid "Your audio level"
msgstr "您的通話音量"
@ -199,6 +175,30 @@ msgstr "重試" @@ -199,6 +175,30 @@ msgstr "重試"
msgid "Download failed."
msgstr "下載失敗"
msgid "Share a YouTube video"
msgstr ""
msgid "Share a file as presentation"
msgstr ""
msgid "Share your screen"
msgstr "共享您的屏幕"
msgid "Chat"
msgstr "聊天"
msgid "Contacts"
msgstr ""
msgid "Mute microphone"
msgstr "關閉麥克風"
msgid "Turn camera off"
msgstr "關閉攝像頭"
msgid "Settings"
msgstr "系統設置"
msgid "Loading presentation ..."
msgstr ""
@ -231,12 +231,12 @@ msgstr "更換房間" @@ -231,12 +231,12 @@ msgstr "更換房間"
msgid "Room"
msgstr "房間"
msgid "Main"
msgstr "住房間"
msgid "Leave room"
msgstr "離開房間"
msgid "Main"
msgstr "住房間"
msgid "Current room"
msgstr "當前房間"
@ -492,6 +492,9 @@ msgstr "" @@ -492,6 +492,9 @@ msgstr ""
msgid "Room history"
msgstr ""
msgid "Please sign in."
msgstr ""
msgid "Videos are shared with everyone in this call."
msgstr ""

61
src/i18n/messages.pot

@ -1,15 +1,15 @@ @@ -1,15 +1,15 @@
# Translations template for Spreed WebRTC.
# Copyright (C) 2014 struktur AG
# Copyright (C) 2015 struktur AG
# This file is distributed under the same license as the Spreed WebRTC
# project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Spreed WebRTC 1.0\n"
"Report-Msgid-Bugs-To: simon@struktur.de\n"
"POT-Creation-Date: 2014-12-08 16:48+0100\n"
"POT-Creation-Date: 2015-01-28 15:06+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,30 +18,6 @@ msgstr "" @@ -18,30 +18,6 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 0.9.6\n"
msgid "Share a YouTube video"
msgstr ""
msgid "Share a file as presentation"
msgstr ""
msgid "Share your screen"
msgstr ""
msgid "Chat"
msgstr ""
msgid "Contacts"
msgstr ""
msgid "Mute microphone"
msgstr ""
msgid "Turn camera off"
msgstr ""
msgid "Settings"
msgstr ""
msgid "Your audio level"
msgstr ""
@ -195,6 +171,30 @@ msgstr "" @@ -195,6 +171,30 @@ msgstr ""
msgid "Download failed."
msgstr ""
msgid "Share a YouTube video"
msgstr ""
msgid "Share a file as presentation"
msgstr ""
msgid "Share your screen"
msgstr ""
msgid "Chat"
msgstr ""
msgid "Contacts"
msgstr ""
msgid "Mute microphone"
msgstr ""
msgid "Turn camera off"
msgstr ""
msgid "Settings"
msgstr ""
msgid "Loading presentation ..."
msgstr ""
@ -227,10 +227,10 @@ msgstr "" @@ -227,10 +227,10 @@ msgstr ""
msgid "Room"
msgstr ""
msgid "Main"
msgid "Leave room"
msgstr ""
msgid "Leave room"
msgid "Main"
msgstr ""
msgid "Current room"
@ -486,6 +486,9 @@ msgstr "" @@ -486,6 +486,9 @@ msgstr ""
msgid "Room history"
msgstr ""
msgid "Please sign in."
msgstr ""
msgid "Videos are shared with everyone in this call."
msgstr ""

13
src/styles/components/_buddylist.scss

@ -94,7 +94,6 @@ @@ -94,7 +94,6 @@
position: relative;
user-select: none;
}
.buddylistloading,
.buddylistempty {
bottom: 0;
color: $font-color-accent;
@ -109,6 +108,18 @@ @@ -109,6 +108,18 @@
text-align: center;
top: 0;
}
.buddylistloading {
bottom: 0;
color: $font-color-accent;
display: none;
font-size: 1.4em;
height: 2em;
margin: auto;
padding:.4em;
position: absolute;
right: 0;
text-align: center;
}
}
.buddy {

2
static/css/main.min.css vendored

File diff suppressed because one or more lines are too long

34
static/js/controllers/mediastreamcontroller.js

@ -40,6 +40,13 @@ define(['jquery', 'underscore', 'angular', 'bigscreen', 'moment', 'sjcl', 'moder @@ -40,6 +40,13 @@ define(['jquery', 'underscore', 'angular', 'bigscreen', 'moment', 'sjcl', 'moder
return translation._("Close this window and disconnect?");
});
$($window).on("unload", function() {
mediaStream.webrtc.doHangup("unload");
if (mediaStream.api.connector) {
mediaStream.api.connector.disabled = true;
}
});
// Enable app full screen listener.
$("#bar .logo").on("doubletap dblclick", _.debounce(function() {
if (BigScreen.enabled) {
@ -401,10 +408,28 @@ define(['jquery', 'underscore', 'angular', 'bigscreen', 'moment', 'sjcl', 'moder @@ -401,10 +408,28 @@ define(['jquery', 'underscore', 'angular', 'bigscreen', 'moment', 'sjcl', 'moder
// Unmark authorization process.
if (data.Userid) {
appData.authorizing(false);
} else if (!appData.authorizing()) {
appData.authorizing(false, data.Userid);
} else {
if (!appData.authorizing()) {
// Trigger user data load when not in authorizing phase.
$scope.loadUserSettings();
} else {
// Wait until authorizing is over and try it then.
var handler = (function() {
return function(event, authorizing, userid) {
if (!authorizing) {
// Turn of handler if we are no longer authorizing.
appData.e.off("authorizing", handler);
handler = null;
if (!userid) {
// Trigger user data load when without user after authorizing phase.
$scope.loadUserSettings();
}
}
}
})();
appData.e.on("authorizing", handler);
}
}
// Select room if settings have an alternative default room.
@ -573,6 +598,11 @@ define(['jquery', 'underscore', 'angular', 'bigscreen', 'moment', 'sjcl', 'moder @@ -573,6 +598,11 @@ define(['jquery', 'underscore', 'angular', 'bigscreen', 'moment', 'sjcl', 'moder
}
});
// Start heartbeat timer.
$window.setInterval(function() {
mediaStream.api.heartbeat(5000, 11500)
}, 1000);
$scope.$on("active", function(event, currentcall) {
console.info("Video state active (assuming connected)", currentcall.id);

3
static/js/directives/audiovideo.js

@ -108,9 +108,8 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/ @@ -108,9 +108,8 @@ define(['jquery', 'underscore', 'text!partials/audiovideo.html', 'text!partials/
// Add created scope.
if (stream === dummy) {
subscope.dummy = true;
} else {
streams[id] = subscope;
}
streams[id] = subscope;
// Render template.
peerTemplate(subscope, function(clonedElement, scope) {

6
static/js/directives/directives.js

@ -47,7 +47,8 @@ define([ @@ -47,7 +47,8 @@ define([
'directives/youtubevideo',
'directives/bfi',
'directives/title',
'directives/welcome'], function(_, onEnter, onEscape, statusMessage, buddyList, buddyPictureCapture, buddyPictureUpload, settings, chat, audioVideo, usability, audioLevel, fileInfo, screenshare, roomBar, socialShare, page, contactRequest, defaultDialog, pdfcanvas, odfcanvas, presentation, youtubevideo, bfi, title, welcome) {
'directives/welcome',
'directives/menu'], function(_, onEnter, onEscape, statusMessage, buddyList, buddyPictureCapture, buddyPictureUpload, settings, chat, audioVideo, usability, audioLevel, fileInfo, screenshare, roomBar, socialShare, page, contactRequest, defaultDialog, pdfcanvas, odfcanvas, presentation, youtubevideo, bfi, title, welcome, menu) {
var directives = {
onEnter: onEnter,
@ -74,7 +75,8 @@ define([ @@ -74,7 +75,8 @@ define([
youtubevideo: youtubevideo,
bfi: bfi,
title: title,
welcome: welcome
welcome: welcome,
menu: menu
};
var initialize = function(angModule) {

46
static/js/directives/menu.js

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
/*
* Spreed WebRTC.
* Copyright (C) 2013-2015 struktur AG
*
* This file is part of Spreed WebRTC.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
"use strict";
define(['text!partials/menu.html'], function(template) {
// menu
return ["mediaStream", function(mediaStream) {
var link = function($scope, $element) {
$scope.modules = mediaStream.config.Modules || [];
$scope.withModule = function(m) {
return $scope.modules.indexOf(m) !== -1;
};
};
return {
restrict: 'E',
replace: true,
template: template,
link: link
}
}];
});

14
static/js/directives/roombar.js

@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
define(['underscore', 'angular', 'text!partials/roombar.html'], function(_, angular, template) {
// roomBar
return ["$window", "rooms", "$timeout", function($window, rooms, $timeout) {
return ["$window", "rooms", "$timeout", "safeApply", function($window, rooms, $timeout, safeApply) {
var link = function($scope, $element) {
@ -51,13 +51,17 @@ define(['underscore', 'angular', 'text!partials/roombar.html'], function(_, angu @@ -51,13 +51,17 @@ define(['underscore', 'angular', 'text!partials/roombar.html'], function(_, angu
};
$scope.$on("room.updated", function(ev, room) {
$scope.currentRoomName = $scope.newRoomName = room.Name;
if ($scope.currentRoomName && !$scope.peer) {
$scope.layout.roombar = true;
safeApply($scope, function(scope) {
scope.currentRoomName = scope.newRoomName = room.Name;
if (scope.currentRoomName && !scope.peer) {
scope.layout.roombar = true;
}
});
});
$scope.$on("room.left", clearRoomName);
$scope.$on("room.left", function() {
safeApply($scope, clearRoomName);
});
$scope.$watch("newRoomName", function(name) {
if (name === $scope.currentRoomName) {

1
static/js/directives/welcome.js

@ -32,6 +32,7 @@ define([], function() { @@ -32,6 +32,7 @@ define([], function() {
$scope.randomRoom = rooms.randomRoom;
$scope.canCreateRooms = rooms.canCreateRooms;
$scope.canJoinRooms = rooms.canJoinRooms;
$scope.joinRoomByName = function(name) {
if ($scope.welcome.$invalid) {
return;

135
static/js/libs/lodash.min.js vendored

@ -1,56 +1,85 @@ @@ -1,56 +1,85 @@
/**
* @license
* Lo-Dash 2.4.1 (Custom Build) lodash.com/license | Underscore.js 1.5.2 underscorejs.org/LICENSE
* Build: `lodash modern -o ./dist/lodash.js`
* lodash 3.0.0 (Custom Build) lodash.com/license | Underscore.js 1.7.0 underscorejs.org/LICENSE
* Build: `lodash modern -o ./lodash.js`
*/
;(function(){function n(n,t,e){e=(e||0)-1;for(var r=n?n.length:0;++e<r;)if(n[e]===t)return e;return-1}function t(t,e){var r=typeof e;if(t=t.l,"boolean"==r||null==e)return t[e]?0:-1;"number"!=r&&"string"!=r&&(r="object");var u="number"==r?e:m+e;return t=(t=t[r])&&t[u],"object"==r?t&&-1<n(t,e)?0:-1:t?0:-1}function e(n){var t=this.l,e=typeof n;if("boolean"==e||null==n)t[n]=true;else{"number"!=e&&"string"!=e&&(e="object");var r="number"==e?n:m+n,t=t[e]||(t[e]={});"object"==e?(t[r]||(t[r]=[])).push(n):t[r]=true
}}function r(n){return n.charCodeAt(0)}function u(n,t){for(var e=n.m,r=t.m,u=-1,o=e.length;++u<o;){var i=e[u],a=r[u];if(i!==a){if(i>a||typeof i=="undefined")return 1;if(i<a||typeof a=="undefined")return-1}}return n.n-t.n}function o(n){var t=-1,r=n.length,u=n[0],o=n[r/2|0],i=n[r-1];if(u&&typeof u=="object"&&o&&typeof o=="object"&&i&&typeof i=="object")return false;for(u=f(),u["false"]=u["null"]=u["true"]=u.undefined=false,o=f(),o.k=n,o.l=u,o.push=e;++t<r;)o.push(n[t]);return o}function i(n){return"\\"+U[n]
}function a(){return h.pop()||[]}function f(){return g.pop()||{k:null,l:null,m:null,"false":false,n:0,"null":false,number:null,object:null,push:null,string:null,"true":false,undefined:false,o:null}}function l(n){n.length=0,h.length<_&&h.push(n)}function c(n){var t=n.l;t&&c(t),n.k=n.l=n.m=n.object=n.number=n.string=n.o=null,g.length<_&&g.push(n)}function p(n,t,e){t||(t=0),typeof e=="undefined"&&(e=n?n.length:0);var r=-1;e=e-t||0;for(var u=Array(0>e?0:e);++r<e;)u[r]=n[t+r];return u}function s(e){function h(n,t,e){if(!n||!V[typeof n])return n;
t=t&&typeof e=="undefined"?t:tt(t,e,3);for(var r=-1,u=V[typeof n]&&Fe(n),o=u?u.length:0;++r<o&&(e=u[r],false!==t(n[e],e,n)););return n}function g(n,t,e){var r;if(!n||!V[typeof n])return n;t=t&&typeof e=="undefined"?t:tt(t,e,3);for(r in n)if(false===t(n[r],r,n))break;return n}function _(n,t,e){var r,u=n,o=u;if(!u)return o;for(var i=arguments,a=0,f=typeof e=="number"?2:i.length;++a<f;)if((u=i[a])&&V[typeof u])for(var l=-1,c=V[typeof u]&&Fe(u),p=c?c.length:0;++l<p;)r=c[l],"undefined"==typeof o[r]&&(o[r]=u[r]);
return o}function U(n,t,e){var r,u=n,o=u;if(!u)return o;var i=arguments,a=0,f=typeof e=="number"?2:i.length;if(3<f&&"function"==typeof i[f-2])var l=tt(i[--f-1],i[f--],2);else 2<f&&"function"==typeof i[f-1]&&(l=i[--f]);for(;++a<f;)if((u=i[a])&&V[typeof u])for(var c=-1,p=V[typeof u]&&Fe(u),s=p?p.length:0;++c<s;)r=p[c],o[r]=l?l(o[r],u[r]):u[r];return o}function H(n){var t,e=[];if(!n||!V[typeof n])return e;for(t in n)me.call(n,t)&&e.push(t);return e}function J(n){return n&&typeof n=="object"&&!Te(n)&&me.call(n,"__wrapped__")?n:new Q(n)
}function Q(n,t){this.__chain__=!!t,this.__wrapped__=n}function X(n){function t(){if(r){var n=p(r);be.apply(n,arguments)}if(this instanceof t){var o=nt(e.prototype),n=e.apply(o,n||arguments);return wt(n)?n:o}return e.apply(u,n||arguments)}var e=n[0],r=n[2],u=n[4];return $e(t,n),t}function Z(n,t,e,r,u){if(e){var o=e(n);if(typeof o!="undefined")return o}if(!wt(n))return n;var i=ce.call(n);if(!K[i])return n;var f=Ae[i];switch(i){case T:case F:return new f(+n);case W:case P:return new f(n);case z:return o=f(n.source,C.exec(n)),o.lastIndex=n.lastIndex,o
}if(i=Te(n),t){var c=!r;r||(r=a()),u||(u=a());for(var s=r.length;s--;)if(r[s]==n)return u[s];o=i?f(n.length):{}}else o=i?p(n):U({},n);return i&&(me.call(n,"index")&&(o.index=n.index),me.call(n,"input")&&(o.input=n.input)),t?(r.push(n),u.push(o),(i?St:h)(n,function(n,i){o[i]=Z(n,t,e,r,u)}),c&&(l(r),l(u)),o):o}function nt(n){return wt(n)?ke(n):{}}function tt(n,t,e){if(typeof n!="function")return Ut;if(typeof t=="undefined"||!("prototype"in n))return n;var r=n.__bindData__;if(typeof r=="undefined"&&(De.funcNames&&(r=!n.name),r=r||!De.funcDecomp,!r)){var u=ge.call(n);
De.funcNames||(r=!O.test(u)),r||(r=E.test(u),$e(n,r))}if(false===r||true!==r&&1&r[1])return n;switch(e){case 1:return function(e){return n.call(t,e)};case 2:return function(e,r){return n.call(t,e,r)};case 3:return function(e,r,u){return n.call(t,e,r,u)};case 4:return function(e,r,u,o){return n.call(t,e,r,u,o)}}return Mt(n,t)}function et(n){function t(){var n=f?i:this;if(u){var h=p(u);be.apply(h,arguments)}return(o||c)&&(h||(h=p(arguments)),o&&be.apply(h,o),c&&h.length<a)?(r|=16,et([e,s?r:-4&r,h,null,i,a])):(h||(h=arguments),l&&(e=n[v]),this instanceof t?(n=nt(e.prototype),h=e.apply(n,h),wt(h)?h:n):e.apply(n,h))
}var e=n[0],r=n[1],u=n[2],o=n[3],i=n[4],a=n[5],f=1&r,l=2&r,c=4&r,s=8&r,v=e;return $e(t,n),t}function rt(e,r){var u=-1,i=st(),a=e?e.length:0,f=a>=b&&i===n,l=[];if(f){var p=o(r);p?(i=t,r=p):f=false}for(;++u<a;)p=e[u],0>i(r,p)&&l.push(p);return f&&c(r),l}function ut(n,t,e,r){r=(r||0)-1;for(var u=n?n.length:0,o=[];++r<u;){var i=n[r];if(i&&typeof i=="object"&&typeof i.length=="number"&&(Te(i)||yt(i))){t||(i=ut(i,t,e));var a=-1,f=i.length,l=o.length;for(o.length+=f;++a<f;)o[l++]=i[a]}else e||o.push(i)}return o
}function ot(n,t,e,r,u,o){if(e){var i=e(n,t);if(typeof i!="undefined")return!!i}if(n===t)return 0!==n||1/n==1/t;if(n===n&&!(n&&V[typeof n]||t&&V[typeof t]))return false;if(null==n||null==t)return n===t;var f=ce.call(n),c=ce.call(t);if(f==D&&(f=q),c==D&&(c=q),f!=c)return false;switch(f){case T:case F:return+n==+t;case W:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case z:case P:return n==oe(t)}if(c=f==$,!c){var p=me.call(n,"__wrapped__"),s=me.call(t,"__wrapped__");if(p||s)return ot(p?n.__wrapped__:n,s?t.__wrapped__:t,e,r,u,o);
if(f!=q)return false;if(f=n.constructor,p=t.constructor,f!=p&&!(dt(f)&&f instanceof f&&dt(p)&&p instanceof p)&&"constructor"in n&&"constructor"in t)return false}for(f=!u,u||(u=a()),o||(o=a()),p=u.length;p--;)if(u[p]==n)return o[p]==t;var v=0,i=true;if(u.push(n),o.push(t),c){if(p=n.length,v=t.length,(i=v==p)||r)for(;v--;)if(c=p,s=t[v],r)for(;c--&&!(i=ot(n[c],s,e,r,u,o)););else if(!(i=ot(n[v],s,e,r,u,o)))break}else g(t,function(t,a,f){return me.call(f,a)?(v++,i=me.call(n,a)&&ot(n[a],t,e,r,u,o)):void 0}),i&&!r&&g(n,function(n,t,e){return me.call(e,t)?i=-1<--v:void 0
});return u.pop(),o.pop(),f&&(l(u),l(o)),i}function it(n,t,e,r,u){(Te(t)?St:h)(t,function(t,o){var i,a,f=t,l=n[o];if(t&&((a=Te(t))||Pe(t))){for(f=r.length;f--;)if(i=r[f]==t){l=u[f];break}if(!i){var c;e&&(f=e(l,t),c=typeof f!="undefined")&&(l=f),c||(l=a?Te(l)?l:[]:Pe(l)?l:{}),r.push(t),u.push(l),c||it(l,t,e,r,u)}}else e&&(f=e(l,t),typeof f=="undefined"&&(f=t)),typeof f!="undefined"&&(l=f);n[o]=l})}function at(n,t){return n+he(Re()*(t-n+1))}function ft(e,r,u){var i=-1,f=st(),p=e?e.length:0,s=[],v=!r&&p>=b&&f===n,h=u||v?a():s;
for(v&&(h=o(h),f=t);++i<p;){var g=e[i],y=u?u(g,i,e):g;(r?!i||h[h.length-1]!==y:0>f(h,y))&&((u||v)&&h.push(y),s.push(g))}return v?(l(h.k),c(h)):u&&l(h),s}function lt(n){return function(t,e,r){var u={};e=J.createCallback(e,r,3),r=-1;var o=t?t.length:0;if(typeof o=="number")for(;++r<o;){var i=t[r];n(u,i,e(i,r,t),t)}else h(t,function(t,r,o){n(u,t,e(t,r,o),o)});return u}}function ct(n,t,e,r,u,o){var i=1&t,a=4&t,f=16&t,l=32&t;if(!(2&t||dt(n)))throw new ie;f&&!e.length&&(t&=-17,f=e=false),l&&!r.length&&(t&=-33,l=r=false);
var c=n&&n.__bindData__;return c&&true!==c?(c=p(c),c[2]&&(c[2]=p(c[2])),c[3]&&(c[3]=p(c[3])),!i||1&c[1]||(c[4]=u),!i&&1&c[1]&&(t|=8),!a||4&c[1]||(c[5]=o),f&&be.apply(c[2]||(c[2]=[]),e),l&&we.apply(c[3]||(c[3]=[]),r),c[1]|=t,ct.apply(null,c)):(1==t||17===t?X:et)([n,t,e,r,u,o])}function pt(n){return Be[n]}function st(){var t=(t=J.indexOf)===Wt?n:t;return t}function vt(n){return typeof n=="function"&&pe.test(n)}function ht(n){var t,e;return n&&ce.call(n)==q&&(t=n.constructor,!dt(t)||t instanceof t)?(g(n,function(n,t){e=t
}),typeof e=="undefined"||me.call(n,e)):false}function gt(n){return We[n]}function yt(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==D||false}function mt(n,t,e){var r=Fe(n),u=r.length;for(t=tt(t,e,3);u--&&(e=r[u],false!==t(n[e],e,n)););return n}function bt(n){var t=[];return g(n,function(n,e){dt(n)&&t.push(e)}),t.sort()}function _t(n){for(var t=-1,e=Fe(n),r=e.length,u={};++t<r;){var o=e[t];u[n[o]]=o}return u}function dt(n){return typeof n=="function"}function wt(n){return!(!n||!V[typeof n])
}function jt(n){return typeof n=="number"||n&&typeof n=="object"&&ce.call(n)==W||false}function kt(n){return typeof n=="string"||n&&typeof n=="object"&&ce.call(n)==P||false}function xt(n){for(var t=-1,e=Fe(n),r=e.length,u=Xt(r);++t<r;)u[t]=n[e[t]];return u}function Ct(n,t,e){var r=-1,u=st(),o=n?n.length:0,i=false;return e=(0>e?Ie(0,o+e):e)||0,Te(n)?i=-1<u(n,t,e):typeof o=="number"?i=-1<(kt(n)?n.indexOf(t,e):u(n,t,e)):h(n,function(n){return++r<e?void 0:!(i=n===t)}),i}function Ot(n,t,e){var r=true;t=J.createCallback(t,e,3),e=-1;
var u=n?n.length:0;if(typeof u=="number")for(;++e<u&&(r=!!t(n[e],e,n)););else h(n,function(n,e,u){return r=!!t(n,e,u)});return r}function Nt(n,t,e){var r=[];t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++e<u;){var o=n[e];t(o,e,n)&&r.push(o)}else h(n,function(n,e,u){t(n,e,u)&&r.push(n)});return r}function It(n,t,e){t=J.createCallback(t,e,3),e=-1;var r=n?n.length:0;if(typeof r!="number"){var u;return h(n,function(n,e,r){return t(n,e,r)?(u=n,false):void 0}),u}for(;++e<r;){var o=n[e];
if(t(o,e,n))return o}}function St(n,t,e){var r=-1,u=n?n.length:0;if(t=t&&typeof e=="undefined"?t:tt(t,e,3),typeof u=="number")for(;++r<u&&false!==t(n[r],r,n););else h(n,t);return n}function Et(n,t,e){var r=n?n.length:0;if(t=t&&typeof e=="undefined"?t:tt(t,e,3),typeof r=="number")for(;r--&&false!==t(n[r],r,n););else{var u=Fe(n),r=u.length;h(n,function(n,e,o){return e=u?u[--r]:--r,t(o[e],e,o)})}return n}function Rt(n,t,e){var r=-1,u=n?n.length:0;if(t=J.createCallback(t,e,3),typeof u=="number")for(var o=Xt(u);++r<u;)o[r]=t(n[r],r,n);
else o=[],h(n,function(n,e,u){o[++r]=t(n,e,u)});return o}function At(n,t,e){var u=-1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&Te(n)){e=-1;for(var i=n.length;++e<i;){var a=n[e];a>o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e>u&&(u=e,o=n)});return o}function Dt(n,t,e,r){if(!n)return e;var u=3>arguments.length;t=J.createCallback(t,r,4);var o=-1,i=n.length;if(typeof i=="number")for(u&&(e=n[++o]);++o<i;)e=t(e,n[o],o,n);else h(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)
});return e}function $t(n,t,e,r){var u=3>arguments.length;return t=J.createCallback(t,r,4),Et(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)}),e}function Tt(n){var t=-1,e=n?n.length:0,r=Xt(typeof e=="number"?e:0);return St(n,function(n){var e=at(0,++t);r[t]=r[e],r[e]=n}),r}function Ft(n,t,e){var r;t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++e<u&&!(r=t(n[e],e,n)););else h(n,function(n,e,u){return!(r=t(n,e,u))});return!!r}function Bt(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=-1;
for(t=J.createCallback(t,e,3);++o<u&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[0]:v;return p(n,0,Se(Ie(0,r),u))}function Wt(t,e,r){if(typeof r=="number"){var u=t?t.length:0;r=0>r?Ie(0,u+r):r||0}else if(r)return r=zt(t,e),t[r]===e?r:-1;return n(t,e,r)}function qt(n,t,e){if(typeof t!="number"&&null!=t){var r=0,u=-1,o=n?n.length:0;for(t=J.createCallback(t,e,3);++u<o&&t(n[u],u,n);)r++}else r=null==t||e?1:Ie(0,t);return p(n,r)}function zt(n,t,e,r){var u=0,o=n?n.length:u;for(e=e?J.createCallback(e,r,1):Ut,t=e(t);u<o;)r=u+o>>>1,e(n[r])<t?u=r+1:o=r;
return u}function Pt(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(e=J.createCallback(e,r,3)),ft(n,t,e)}function Kt(){for(var n=1<arguments.length?arguments:arguments[0],t=-1,e=n?At(Ve(n,"length")):0,r=Xt(0>e?0:e);++t<e;)r[t]=Ve(n,t);return r}function Lt(n,t){var e=-1,r=n?n.length:0,u={};for(t||!r||Te(n[0])||(t=[]);++e<r;){var o=n[e];t?u[o]=t[e]:o&&(u[o[0]]=o[1])}return u}function Mt(n,t){return 2<arguments.length?ct(n,17,p(arguments,2),null,t):ct(n,1,null,null,t)
}function Vt(n,t,e){function r(){c&&ve(c),i=c=p=v,(g||h!==t)&&(s=Ue(),a=n.apply(l,o),c||i||(o=l=null))}function u(){var e=t-(Ue()-f);0<e?c=_e(u,e):(i&&ve(i),e=p,i=c=p=v,e&&(s=Ue(),a=n.apply(l,o),c||i||(o=l=null)))}var o,i,a,f,l,c,p,s=0,h=false,g=true;if(!dt(n))throw new ie;if(t=Ie(0,t)||0,true===e)var y=true,g=false;else wt(e)&&(y=e.leading,h="maxWait"in e&&(Ie(t,e.maxWait)||0),g="trailing"in e?e.trailing:g);return function(){if(o=arguments,f=Ue(),l=this,p=g&&(c||!y),false===h)var e=y&&!c;else{i||y||(s=f);var v=h-(f-s),m=0>=v;
m?(i&&(i=ve(i)),s=f,a=n.apply(l,o)):i||(i=_e(r,v))}return m&&c?c=ve(c):c||t===h||(c=_e(u,t)),e&&(m=true,a=n.apply(l,o)),!m||c||i||(o=l=null),a}}function Ut(n){return n}function Gt(n,t,e){var r=true,u=t&&bt(t);t&&(e||u.length)||(null==e&&(e=t),o=Q,t=n,n=J,u=bt(t)),false===e?r=false:wt(e)&&"chain"in e&&(r=e.chain);var o=n,i=dt(o);St(u,function(e){var u=n[e]=t[e];i&&(o.prototype[e]=function(){var t=this.__chain__,e=this.__wrapped__,i=[e];if(be.apply(i,arguments),i=u.apply(n,i),r||t){if(e===i&&wt(i))return this;
i=new o(i),i.__chain__=t}return i})})}function Ht(){}function Jt(n){return function(t){return t[n]}}function Qt(){return this.__wrapped__}e=e?Y.defaults(G.Object(),e,Y.pick(G,A)):G;var Xt=e.Array,Yt=e.Boolean,Zt=e.Date,ne=e.Function,te=e.Math,ee=e.Number,re=e.Object,ue=e.RegExp,oe=e.String,ie=e.TypeError,ae=[],fe=re.prototype,le=e._,ce=fe.toString,pe=ue("^"+oe(ce).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$"),se=te.ceil,ve=e.clearTimeout,he=te.floor,ge=ne.prototype.toString,ye=vt(ye=re.getPrototypeOf)&&ye,me=fe.hasOwnProperty,be=ae.push,_e=e.setTimeout,de=ae.splice,we=ae.unshift,je=function(){try{var n={},t=vt(t=re.defineProperty)&&t,e=t(n,n,n)&&t
}catch(r){}return e}(),ke=vt(ke=re.create)&&ke,xe=vt(xe=Xt.isArray)&&xe,Ce=e.isFinite,Oe=e.isNaN,Ne=vt(Ne=re.keys)&&Ne,Ie=te.max,Se=te.min,Ee=e.parseInt,Re=te.random,Ae={};Ae[$]=Xt,Ae[T]=Yt,Ae[F]=Zt,Ae[B]=ne,Ae[q]=re,Ae[W]=ee,Ae[z]=ue,Ae[P]=oe,Q.prototype=J.prototype;var De=J.support={};De.funcDecomp=!vt(e.a)&&E.test(s),De.funcNames=typeof ne.name=="string",J.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:N,variable:"",imports:{_:J}},ke||(nt=function(){function n(){}return function(t){if(wt(t)){n.prototype=t;
var r=new n;n.prototype=null}return r||e.Object()}}());var $e=je?function(n,t){M.value=t,je(n,"__bindData__",M)}:Ht,Te=xe||function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==$||false},Fe=Ne?function(n){return wt(n)?Ne(n):[]}:H,Be={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},We=_t(Be),qe=ue("("+Fe(We).join("|")+")","g"),ze=ue("["+Fe(Be).join("")+"]","g"),Pe=ye?function(n){if(!n||ce.call(n)!=q)return false;var t=n.valueOf,e=vt(t)&&(e=ye(t))&&ye(e);return e?n==e||ye(n)==e:ht(n)
}:ht,Ke=lt(function(n,t,e){me.call(n,e)?n[e]++:n[e]=1}),Le=lt(function(n,t,e){(me.call(n,e)?n[e]:n[e]=[]).push(t)}),Me=lt(function(n,t,e){n[e]=t}),Ve=Rt,Ue=vt(Ue=Zt.now)&&Ue||function(){return(new Zt).getTime()},Ge=8==Ee(d+"08")?Ee:function(n,t){return Ee(kt(n)?n.replace(I,""):n,t||0)};return J.after=function(n,t){if(!dt(t))throw new ie;return function(){return 1>--n?t.apply(this,arguments):void 0}},J.assign=U,J.at=function(n){for(var t=arguments,e=-1,r=ut(t,true,false,1),t=t[2]&&t[2][t[1]]===n?1:r.length,u=Xt(t);++e<t;)u[e]=n[r[e]];
return u},J.bind=Mt,J.bindAll=function(n){for(var t=1<arguments.length?ut(arguments,true,false,1):bt(n),e=-1,r=t.length;++e<r;){var u=t[e];n[u]=ct(n[u],1,null,null,n)}return n},J.bindKey=function(n,t){return 2<arguments.length?ct(t,19,p(arguments,2),null,n):ct(t,3,null,null,n)},J.chain=function(n){return n=new Q(n),n.__chain__=true,n},J.compact=function(n){for(var t=-1,e=n?n.length:0,r=[];++t<e;){var u=n[t];u&&r.push(u)}return r},J.compose=function(){for(var n=arguments,t=n.length;t--;)if(!dt(n[t]))throw new ie;
return function(){for(var t=arguments,e=n.length;e--;)t=[n[e].apply(this,t)];return t[0]}},J.constant=function(n){return function(){return n}},J.countBy=Ke,J.create=function(n,t){var e=nt(n);return t?U(e,t):e},J.createCallback=function(n,t,e){var r=typeof n;if(null==n||"function"==r)return tt(n,t,e);if("object"!=r)return Jt(n);var u=Fe(n),o=u[0],i=n[o];return 1!=u.length||i!==i||wt(i)?function(t){for(var e=u.length,r=false;e--&&(r=ot(t[u[e]],n[u[e]],null,true)););return r}:function(n){return n=n[o],i===n&&(0!==i||1/i==1/n)
}},J.curry=function(n,t){return t=typeof t=="number"?t:+t||n.length,ct(n,4,null,null,null,t)},J.debounce=Vt,J.defaults=_,J.defer=function(n){if(!dt(n))throw new ie;var t=p(arguments,1);return _e(function(){n.apply(v,t)},1)},J.delay=function(n,t){if(!dt(n))throw new ie;var e=p(arguments,2);return _e(function(){n.apply(v,e)},t)},J.difference=function(n){return rt(n,ut(arguments,true,true,1))},J.filter=Nt,J.flatten=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=typeof t!="function"&&r&&r[t]===n?null:t,t=false),null!=e&&(n=Rt(n,e,r)),ut(n,t)
},J.forEach=St,J.forEachRight=Et,J.forIn=g,J.forInRight=function(n,t,e){var r=[];g(n,function(n,t){r.push(t,n)});var u=r.length;for(t=tt(t,e,3);u--&&false!==t(r[u--],r[u],n););return n},J.forOwn=h,J.forOwnRight=mt,J.functions=bt,J.groupBy=Le,J.indexBy=Me,J.initial=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else r=null==t||e?1:t||r;return p(n,0,Se(Ie(0,u-r),u))},J.intersection=function(){for(var e=[],r=-1,u=arguments.length,i=a(),f=st(),p=f===n,s=a();++r<u;){var v=arguments[r];
(Te(v)||yt(v))&&(e.push(v),i.push(p&&v.length>=b&&o(r?e[r]:s)))}var p=e[0],h=-1,g=p?p.length:0,y=[];n:for(;++h<g;){var m=i[0],v=p[h];if(0>(m?t(m,v):f(s,v))){for(r=u,(m||s).push(v);--r;)if(m=i[r],0>(m?t(m,v):f(e[r],v)))continue n;y.push(v)}}for(;u--;)(m=i[u])&&c(m);return l(i),l(s),y},J.invert=_t,J.invoke=function(n,t){var e=p(arguments,2),r=-1,u=typeof t=="function",o=n?n.length:0,i=Xt(typeof o=="number"?o:0);return St(n,function(n){i[++r]=(u?t:n[t]).apply(n,e)}),i},J.keys=Fe,J.map=Rt,J.mapValues=function(n,t,e){var r={};
return t=J.createCallback(t,e,3),h(n,function(n,e,u){r[e]=t(n,e,u)}),r},J.max=At,J.memoize=function(n,t){function e(){var r=e.cache,u=t?t.apply(this,arguments):m+arguments[0];return me.call(r,u)?r[u]:r[u]=n.apply(this,arguments)}if(!dt(n))throw new ie;return e.cache={},e},J.merge=function(n){var t=arguments,e=2;if(!wt(n))return n;if("number"!=typeof t[2]&&(e=t.length),3<e&&"function"==typeof t[e-2])var r=tt(t[--e-1],t[e--],2);else 2<e&&"function"==typeof t[e-1]&&(r=t[--e]);for(var t=p(arguments,1,e),u=-1,o=a(),i=a();++u<e;)it(n,t[u],r,o,i);
return l(o),l(i),n},J.min=function(n,t,e){var u=1/0,o=u;if(typeof t!="function"&&e&&e[t]===n&&(t=null),null==t&&Te(n)){e=-1;for(var i=n.length;++e<i;){var a=n[e];a<o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e<u&&(u=e,o=n)});return o},J.omit=function(n,t,e){var r={};if(typeof t!="function"){var u=[];g(n,function(n,t){u.push(t)});for(var u=rt(u,ut(arguments,true,false,1)),o=-1,i=u.length;++o<i;){var a=u[o];r[a]=n[a]}}else t=J.createCallback(t,e,3),g(n,function(n,e,u){t(n,e,u)||(r[e]=n)
});return r},J.once=function(n){var t,e;if(!dt(n))throw new ie;return function(){return t?e:(t=true,e=n.apply(this,arguments),n=null,e)}},J.pairs=function(n){for(var t=-1,e=Fe(n),r=e.length,u=Xt(r);++t<r;){var o=e[t];u[t]=[o,n[o]]}return u},J.partial=function(n){return ct(n,16,p(arguments,1))},J.partialRight=function(n){return ct(n,32,null,p(arguments,1))},J.pick=function(n,t,e){var r={};if(typeof t!="function")for(var u=-1,o=ut(arguments,true,false,1),i=wt(n)?o.length:0;++u<i;){var a=o[u];a in n&&(r[a]=n[a])
}else t=J.createCallback(t,e,3),g(n,function(n,e,u){t(n,e,u)&&(r[e]=n)});return r},J.pluck=Ve,J.property=Jt,J.pull=function(n){for(var t=arguments,e=0,r=t.length,u=n?n.length:0;++e<r;)for(var o=-1,i=t[e];++o<u;)n[o]===i&&(de.call(n,o--,1),u--);return n},J.range=function(n,t,e){n=+n||0,e=typeof e=="number"?e:+e||1,null==t&&(t=n,n=0);var r=-1;t=Ie(0,se((t-n)/(e||1)));for(var u=Xt(t);++r<t;)u[r]=n,n+=e;return u},J.reject=function(n,t,e){return t=J.createCallback(t,e,3),Nt(n,function(n,e,r){return!t(n,e,r)
})},J.remove=function(n,t,e){var r=-1,u=n?n.length:0,o=[];for(t=J.createCallback(t,e,3);++r<u;)e=n[r],t(e,r,n)&&(o.push(e),de.call(n,r--,1),u--);return o},J.rest=qt,J.shuffle=Tt,J.sortBy=function(n,t,e){var r=-1,o=Te(t),i=n?n.length:0,p=Xt(typeof i=="number"?i:0);for(o||(t=J.createCallback(t,e,3)),St(n,function(n,e,u){var i=p[++r]=f();o?i.m=Rt(t,function(t){return n[t]}):(i.m=a())[0]=t(n,e,u),i.n=r,i.o=n}),i=p.length,p.sort(u);i--;)n=p[i],p[i]=n.o,o||l(n.m),c(n);return p},J.tap=function(n,t){return t(n),n
},J.throttle=function(n,t,e){var r=true,u=true;if(!dt(n))throw new ie;return false===e?r=false:wt(e)&&(r="leading"in e?e.leading:r,u="trailing"in e?e.trailing:u),L.leading=r,L.maxWait=t,L.trailing=u,Vt(n,t,L)},J.times=function(n,t,e){n=-1<(n=+n)?n:0;var r=-1,u=Xt(n);for(t=tt(t,e,1);++r<n;)u[r]=t(r);return u},J.toArray=function(n){return n&&typeof n.length=="number"?p(n):xt(n)},J.transform=function(n,t,e,r){var u=Te(n);if(null==e)if(u)e=[];else{var o=n&&n.constructor;e=nt(o&&o.prototype)}return t&&(t=J.createCallback(t,r,4),(u?St:h)(n,function(n,r,u){return t(e,n,r,u)
})),e},J.union=function(){return ft(ut(arguments,true,true))},J.uniq=Pt,J.values=xt,J.where=Nt,J.without=function(n){return rt(n,p(arguments,1))},J.wrap=function(n,t){return ct(t,16,[n])},J.xor=function(){for(var n=-1,t=arguments.length;++n<t;){var e=arguments[n];if(Te(e)||yt(e))var r=r?ft(rt(r,e).concat(rt(e,r))):e}return r||[]},J.zip=Kt,J.zipObject=Lt,J.collect=Rt,J.drop=qt,J.each=St,J.eachRight=Et,J.extend=U,J.methods=bt,J.object=Lt,J.select=Nt,J.tail=qt,J.unique=Pt,J.unzip=Kt,Gt(J),J.clone=function(n,t,e,r){return typeof t!="boolean"&&null!=t&&(r=e,e=t,t=false),Z(n,t,typeof e=="function"&&tt(e,r,1))
},J.cloneDeep=function(n,t,e){return Z(n,true,typeof t=="function"&&tt(t,e,1))},J.contains=Ct,J.escape=function(n){return null==n?"":oe(n).replace(ze,pt)},J.every=Ot,J.find=It,J.findIndex=function(n,t,e){var r=-1,u=n?n.length:0;for(t=J.createCallback(t,e,3);++r<u;)if(t(n[r],r,n))return r;return-1},J.findKey=function(n,t,e){var r;return t=J.createCallback(t,e,3),h(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},J.findLast=function(n,t,e){var r;return t=J.createCallback(t,e,3),Et(n,function(n,e,u){return t(n,e,u)?(r=n,false):void 0
}),r},J.findLastIndex=function(n,t,e){var r=n?n.length:0;for(t=J.createCallback(t,e,3);r--;)if(t(n[r],r,n))return r;return-1},J.findLastKey=function(n,t,e){var r;return t=J.createCallback(t,e,3),mt(n,function(n,e,u){return t(n,e,u)?(r=e,false):void 0}),r},J.has=function(n,t){return n?me.call(n,t):false},J.identity=Ut,J.indexOf=Wt,J.isArguments=yt,J.isArray=Te,J.isBoolean=function(n){return true===n||false===n||n&&typeof n=="object"&&ce.call(n)==T||false},J.isDate=function(n){return n&&typeof n=="object"&&ce.call(n)==F||false
},J.isElement=function(n){return n&&1===n.nodeType||false},J.isEmpty=function(n){var t=true;if(!n)return t;var e=ce.call(n),r=n.length;return e==$||e==P||e==D||e==q&&typeof r=="number"&&dt(n.splice)?!r:(h(n,function(){return t=false}),t)},J.isEqual=function(n,t,e,r){return ot(n,t,typeof e=="function"&&tt(e,r,2))},J.isFinite=function(n){return Ce(n)&&!Oe(parseFloat(n))},J.isFunction=dt,J.isNaN=function(n){return jt(n)&&n!=+n},J.isNull=function(n){return null===n},J.isNumber=jt,J.isObject=wt,J.isPlainObject=Pe,J.isRegExp=function(n){return n&&typeof n=="object"&&ce.call(n)==z||false
},J.isString=kt,J.isUndefined=function(n){return typeof n=="undefined"},J.lastIndexOf=function(n,t,e){var r=n?n.length:0;for(typeof e=="number"&&(r=(0>e?Ie(0,r+e):Se(e,r-1))+1);r--;)if(n[r]===t)return r;return-1},J.mixin=Gt,J.noConflict=function(){return e._=le,this},J.noop=Ht,J.now=Ue,J.parseInt=Ge,J.random=function(n,t,e){var r=null==n,u=null==t;return null==e&&(typeof n=="boolean"&&u?(e=n,n=1):u||typeof t!="boolean"||(e=t,u=true)),r&&u&&(t=1),n=+n||0,u?(t=n,n=0):t=+t||0,e||n%1||t%1?(e=Re(),Se(n+e*(t-n+parseFloat("1e-"+((e+"").length-1))),t)):at(n,t)
},J.reduce=Dt,J.reduceRight=$t,J.result=function(n,t){if(n){var e=n[t];return dt(e)?n[t]():e}},J.runInContext=s,J.size=function(n){var t=n?n.length:0;return typeof t=="number"?t:Fe(n).length},J.some=Ft,J.sortedIndex=zt,J.template=function(n,t,e){var r=J.templateSettings;n=oe(n||""),e=_({},e,r);var u,o=_({},e.imports,r.imports),r=Fe(o),o=xt(o),a=0,f=e.interpolate||S,l="__p+='",f=ue((e.escape||S).source+"|"+f.source+"|"+(f===N?x:S).source+"|"+(e.evaluate||S).source+"|$","g");n.replace(f,function(t,e,r,o,f,c){return r||(r=o),l+=n.slice(a,c).replace(R,i),e&&(l+="'+__e("+e+")+'"),f&&(u=true,l+="';"+f+";\n__p+='"),r&&(l+="'+((__t=("+r+"))==null?'':__t)+'"),a=c+t.length,t
}),l+="';",f=e=e.variable,f||(e="obj",l="with("+e+"){"+l+"}"),l=(u?l.replace(w,""):l).replace(j,"$1").replace(k,"$1;"),l="function("+e+"){"+(f?"":e+"||("+e+"={});")+"var __t,__p='',__e=_.escape"+(u?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}";try{var c=ne(r,"return "+l).apply(v,o)}catch(p){throw p.source=l,p}return t?c(t):(c.source=l,c)},J.unescape=function(n){return null==n?"":oe(n).replace(qe,gt)},J.uniqueId=function(n){var t=++y;return oe(null==n?"":n)+t
},J.all=Ot,J.any=Ft,J.detect=It,J.findWhere=It,J.foldl=Dt,J.foldr=$t,J.include=Ct,J.inject=Dt,Gt(function(){var n={};return h(J,function(t,e){J.prototype[e]||(n[e]=t)}),n}(),false),J.first=Bt,J.last=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[u-1]:v;return p(n,Ie(0,u-r))},J.sample=function(n,t,e){return n&&typeof n.length!="number"&&(n=xt(n)),null==t||e?n?n[at(0,n.length-1)]:v:(n=Tt(n),n.length=Se(Ie(0,t),n.length),n)
},J.take=Bt,J.head=Bt,h(J,function(n,t){var e="sample"!==t;J.prototype[t]||(J.prototype[t]=function(t,r){var u=this.__chain__,o=n(this.__wrapped__,t,r);return u||null!=t&&(!r||e&&typeof t=="function")?new Q(o,u):o})}),J.VERSION="2.4.1",J.prototype.chain=function(){return this.__chain__=true,this},J.prototype.toString=function(){return oe(this.__wrapped__)},J.prototype.value=Qt,J.prototype.valueOf=Qt,St(["join","pop","shift"],function(n){var t=ae[n];J.prototype[n]=function(){var n=this.__chain__,e=t.apply(this.__wrapped__,arguments);
return n?new Q(e,n):e}}),St(["push","reverse","sort","unshift"],function(n){var t=ae[n];J.prototype[n]=function(){return t.apply(this.__wrapped__,arguments),this}}),St(["concat","slice","splice"],function(n){var t=ae[n];J.prototype[n]=function(){return new Q(t.apply(this.__wrapped__,arguments),this.__chain__)}}),J}var v,h=[],g=[],y=0,m=+new Date+"",b=75,_=40,d=" \t\x0B\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",w=/\b__p\+='';/g,j=/\b(__p\+=)''\+/g,k=/(__e\(.*?\)|\b__t\))\+'';/g,x=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,C=/\w*$/,O=/^\s*function[ \n\r\t]+\w/,N=/<%=([\s\S]+?)%>/g,I=RegExp("^["+d+"]*0+(?=.$)"),S=/($^)/,E=/\bthis\b/,R=/['\n\r\t\u2028\u2029\\]/g,A="Array Boolean Date Function Math Number Object RegExp String _ attachEvent clearTimeout isFinite isNaN parseInt setTimeout".split(" "),D="[object Arguments]",$="[object Array]",T="[object Boolean]",F="[object Date]",B="[object Function]",W="[object Number]",q="[object Object]",z="[object RegExp]",P="[object String]",K={};
K[B]=false,K[D]=K[$]=K[T]=K[F]=K[W]=K[q]=K[z]=K[P]=true;var L={leading:false,maxWait:0,trailing:false},M={configurable:false,enumerable:false,value:null,writable:false},V={"boolean":false,"function":true,object:true,number:false,string:false,undefined:false},U={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},G=V[typeof window]&&window||this,H=V[typeof exports]&&exports&&!exports.nodeType&&exports,J=V[typeof module]&&module&&!module.nodeType&&module,Q=J&&J.exports===H&&H,X=V[typeof global]&&global;!X||X.global!==X&&X.window!==X||(G=X);
var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G._=Y, define(function(){return Y})):H&&J?Q?(J.exports=Y)._=Y:H._=Y:G._=Y}).call(this);
;(function(){function n(n,t){if(n!==t){var r=n===n,e=t===t;if(n>t||!r||typeof n=="undefined"&&e)return 1;if(n<t||!e||typeof t=="undefined"&&r)return-1}return 0}function t(n,t,r){if(t!==t)return p(n,r);r=(r||0)-1;for(var e=n.length;++r<e;)if(n[r]===t)return r;return-1}function r(n,t){var r=n.length;for(n.sort(t);r--;)n[r]=n[r].c;return n}function e(n){return typeof n=="string"?n:null==n?"":n+""}function u(n){return n.charCodeAt(0)}function o(n,t){for(var r=-1,e=n.length;++r<e&&-1<t.indexOf(n.charAt(r)););return r
}function i(n,t){for(var r=n.length;r--&&-1<t.indexOf(n.charAt(r)););return r}function a(t,r){return n(t.a,r.a)||t.b-r.b}function f(t,r){for(var e=-1,u=t.a,o=r.a,i=u.length;++e<i;){var a=n(u[e],o[e]);if(a)return a}return t.b-r.b}function c(n){return Wt[n]}function l(n){return Nt[n]}function s(n){return"\\"+Lt[n]}function p(n,t,r){var e=n.length;for(t=r?t||e:(t||0)-1;r?t--:++t<e;){var u=n[t];if(u!==u)return t}return-1}function h(n){return n&&typeof n=="object"||false}function g(n){return 160>=n&&9<=n&&13>=n||32==n||160==n||5760==n||6158==n||8192<=n&&(8202>=n||8232==n||8233==n||8239==n||8287==n||12288==n||65279==n)
}function v(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r<e;)n[r]===t&&(n[r]=B,o[++u]=r);return o}function d(n){for(var t=-1,r=n.length;++t<r&&g(n.charCodeAt(t)););return t}function y(n){for(var t=n.length;t--&&g(n.charCodeAt(t)););return t}function _(n){return Ft[n]}function m(g){function Wt(n){if(h(n)&&!To(n)){if(n instanceof Nt)return n;if(Nu.call(n,"__wrapped__"))return new Nt(n.__wrapped__,n.__chain__,zt(n.__actions__))}return new Nt(n)}function Nt(n,t,r){this.__actions__=r||[],this.__chain__=!!t,this.__wrapped__=n
}function Ft(n){this.actions=null,this.dir=1,this.dropCount=0,this.filtered=false,this.iteratees=null,this.takeCount=lo,this.views=null,this.wrapped=n}function Ut(){this.__data__={}}function Lt(n){var t=n?n.length:0;for(this.data={hash:no(null),set:new Yu};t--;)this.push(n[t])}function Bt(n,t){var r=n.data;return(typeof t=="string"||Ge(t)?r.set.has(t):r.hash[t])?0:-1}function zt(n,t){var r=-1,e=n.length;for(t||(t=mu(e));++r<e;)t[r]=n[r];return t}function Mt(n,t){for(var r=-1,e=n.length;++r<e&&false!==t(n[r],r,n););return n
}function qt(n,t){for(var r=-1,e=n.length;++r<e;)if(!t(n[r],r,n))return false;return true}function Pt(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r<e;){var i=n[r];t(i,r,n)&&(o[++u]=i)}return o}function Kt(n,t){for(var r=-1,e=n.length,u=mu(e);++r<e;)u[r]=t(n[r],r,n);return u}function Vt(n){for(var t=-1,r=n.length,e=co;++t<r;){var u=n[t];u>e&&(e=u)}return e}function Yt(n,t,r,e){var u=-1,o=n.length;for(e&&o&&(r=n[++u]);++u<o;)r=t(r,n[u],u,n);return r}function Zt(n,t,r,e){var u=n.length;for(e&&u&&(r=n[--u]);u--;)r=t(r,n[u],u,n);
return r}function Gt(n,t){for(var r=-1,e=n.length;++r<e;)if(t(n[r],r,n))return true;return false}function Jt(n,t){return typeof n=="undefined"?t:n}function Xt(n,t,r,e){return typeof n!="undefined"&&Nu.call(e,r)?n:t}function Ht(n,t,r){var e=Fo(t);if(!r)return nr(t,n,e);for(var u=-1,o=e.length;++u<o;){var i=e[u],a=n[i],f=r(a,t[i],i,n,t);(f===f?f===a:a!==a)&&(typeof a!="undefined"||i in n)||(n[i]=f)}return n}function Qt(n,t){for(var r=-1,e=n.length,u=ue(e),o=t.length,i=mu(o);++r<o;){var a=t[r];u?(a=parseFloat(a),i[r]=re(a,e)?n[a]:b):i[r]=n[a]
}return i}function nr(n,t,r){r||(r=t,t={});for(var e=-1,u=r.length;++e<u;){var o=r[e];t[o]=n[o]}return t}function tr(n,t,r){var u=typeof n;if("function"==u){if(u=typeof t!="undefined"){var u=Wt.support,o=!(u.funcNames?n.name:u.funcDecomp);if(!o){var i=Su.call(n);u.funcNames||(o=!_t.test(i)),o||(o=kt.test(i)||Je(n),bo(n,o))}u=o}n=u?Wr(n,t,r):n}else n=null==n?gu:"object"==u?wr(n,!r):Ar(r?e(n):n);return n}function rr(n,t,r,e,u,o,i){var a;if(r&&(a=u?r(n,e,u):r(n)),typeof a!="undefined")return a;if(!Ge(n))return n;
if(e=To(n)){if(a=Qr(n),!t)return zt(n,a)}else{var f=Uu.call(n),c=f==K;if(f!=Y&&f!=z&&(!c||u))return Tt[f]?te(n,f,t):u?n:{};if(a=ne(c?{}:n),!t)return nr(n,a,Fo(n))}for(o||(o=[]),i||(i=[]),u=o.length;u--;)if(o[u]==n)return i[u];return o.push(n),i.push(a),(e?Mt:gr)(n,function(e,u){a[u]=rr(e,t,r,u,n,o,i)}),a}function er(n,t,r,e){if(!Ze(n))throw new Iu($);return Zu(function(){n.apply(b,Er(r,e))},t)}function ur(n,r){var e=n?n.length:0,u=[];if(!e)return u;var o=-1,i=Hr(),a=i==t,f=a&&200<=r.length&&wo(r),c=r.length;
f&&(i=Bt,a=false,r=f);n:for(;++o<e;)if(f=n[o],a&&f===f){for(var l=c;l--;)if(r[l]===f)continue n;u.push(f)}else 0>i(r,f)&&u.push(f);return u}function or(n,t){var r=n?n.length:0;if(!ue(r))return gr(n,t);for(var e=-1,u=se(n);++e<r&&false!==t(u[e],e,u););return n}function ir(n,t){var r=n?n.length:0;if(!ue(r))return vr(n,t);for(var e=se(n);r--&&false!==t(e[r],r,e););return n}function ar(n,t){var r=true;return or(n,function(n,e,u){return r=!!t(n,e,u)}),r}function fr(n,t){var r=[];return or(n,function(n,e,u){t(n,e,u)&&r.push(n)
}),r}function cr(n,t,r,e){var u;return r(n,function(n,r,o){return t(n,r,o)?(u=e?r:n,false):void 0}),u}function lr(n,t,r,e){e=(e||0)-1;for(var u=n.length,o=-1,i=[];++e<u;){var a=n[e];if(h(a)&&ue(a.length)&&(To(a)||Ke(a))){t&&(a=lr(a,t,r));var f=-1,c=a.length;for(i.length+=c;++f<c;)i[++o]=a[f]}else r||(i[++o]=a)}return i}function sr(n,t,r){var e=-1,u=se(n);r=r(n);for(var o=r.length;++e<o;){var i=r[e];if(false===t(u[i],i,u))break}return n}function pr(n,t,r){var e=se(n);r=r(n);for(var u=r.length;u--;){var o=r[u];
if(false===t(e[o],o,e))break}return n}function hr(n,t){sr(n,t,eu)}function gr(n,t){return sr(n,t,Fo)}function vr(n,t){return pr(n,t,Fo)}function dr(n,t){for(var r=-1,e=t.length,u=-1,o=[];++r<e;){var i=t[r];Ze(n[i])&&(o[++u]=i)}return o}function yr(n,t,r){var e=-1,u=typeof t=="function",o=n?n.length:0,i=ue(o)?mu(o):[];return or(n,function(n){var o=u?t:null!=n&&n[t];i[++e]=o?o.apply(n,r):b}),i}function _r(n,t,r,e,u,o){if(n===t)return 0!==n||1/n==1/t;var i=typeof n,a=typeof t;if("function"!=i&&"object"!=i&&"function"!=a&&"object"!=a||null==n||null==t)n=n!==n&&t!==t;
else n:{var i=_r,a=To(n),f=To(t),c=D,l=D;a||(c=Uu.call(n),c==z?c=Y:c!=Y&&(a=nu(n))),f||(l=Uu.call(t),l==z?l=Y:l!=Y&&nu(t));var s=c==Y,f=l==Y,l=c==l;if(!l||a||s)if(c=s&&Nu.call(n,"__wrapped__"),f=f&&Nu.call(t,"__wrapped__"),c||f)n=i(c?n.value():n,f?t.value():t,r,e,u,o);else if(l){for(u||(u=[]),o||(o=[]),c=u.length;c--;)if(u[c]==n){n=o[c]==t;break n}u.push(n),o.push(t),n=(a?Yr:Gr)(n,t,i,r,e,u,o),u.pop(),o.pop()}else n=false;else n=Zr(n,t,c)}return n}function mr(n,t,r,e,u){var o=t.length;if(null==n)return!o;
for(var i=-1,a=!u;++i<o;)if(a&&e[i]?r[i]!==n[t[i]]:!Nu.call(n,t[i]))return false;for(i=-1;++i<o;){var f=t[i];if(a&&e[i])f=Nu.call(n,f);else{var c=n[f],l=r[i],f=u?u(c,l,f):b;typeof f=="undefined"&&(f=_r(l,c,u,true))}if(!f)return false}return true}function br(n,t){var r=[];return or(n,function(n,e,u){r.push(t(n,e,u))}),r}function wr(n,t){var r=Fo(n),e=r.length;if(1==e){var u=r[0],o=n[u];if(oe(o))return function(n){return null!=n&&o===n[u]&&Nu.call(n,u)}}t&&(n=rr(n,true));for(var i=mu(e),a=mu(e);e--;)o=n[r[e]],i[e]=o,a[e]=oe(o);
return function(n){return mr(n,r,i,a)}}function xr(n,t,r,e,u){var o=ue(t.length)&&(To(t)||nu(t));return(o?Mt:gr)(t,function(t,i,a){if(h(t)){e||(e=[]),u||(u=[]);n:{t=e;for(var f=u,c=t.length,l=a[i];c--;)if(t[c]==l){n[i]=f[c],i=void 0;break n}c=n[i],a=r?r(c,l,i,n,a):b;var s=typeof a=="undefined";s&&(a=l,ue(l.length)&&(To(l)||nu(l))?a=To(c)?c:c?zt(c):[]:(Wo(l)||Ke(l))&&(a=Ke(c)?tu(c):Wo(c)?c:{})),t.push(l),f.push(a),s?n[i]=xr(a,l,r,t,f):(a===a?a!==c:c===c)&&(n[i]=a),i=void 0}return i}f=n[i],a=r?r(f,t,i,n,a):b,(l=typeof a=="undefined")&&(a=t),!o&&typeof a=="undefined"||!l&&(a===a?a===f:f!==f)||(n[i]=a)
}),n}function Ar(n){return function(t){return null==t?b:t[n]}}function jr(n,t){return n+qu(fo()*(t-n+1))}function kr(n,t,r,e,u){return u(n,function(n,u,o){r=e?(e=false,n):t(r,n,u,o)}),r}function Er(n,t,r){var e=-1,u=n.length;for(t=null==t?0:+t||0,0>t&&(t=-t>u?0:u+t),r=typeof r=="undefined"||r>u?u:+r||0,0>r&&(r+=u),u=t>r?0:r-t,r=mu(u);++e<u;)r[e]=n[e+t];return r}function Rr(n,t){var r;return or(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}function Ir(n,r){var e=-1,u=Hr(),o=n.length,i=u==t,a=i&&200<=o,f=a&&wo(),c=[];
f?(u=Bt,i=false):(a=false,f=r?[]:c);n:for(;++e<o;){var l=n[e],s=r?r(l,e,n):l;if(i&&l===l){for(var p=f.length;p--;)if(f[p]===s)continue n;r&&f.push(s),c.push(l)}else 0>u(f,s)&&((r||a)&&f.push(s),c.push(l))}return c}function Or(n,t){for(var r=-1,e=t.length,u=mu(e);++r<e;)u[r]=n[t[r]];return u}function Cr(n,t){var r=n;r instanceof Ft&&(r=r.value());for(var e=-1,u=t.length;++e<u;){var r=[r],o=t[e];Ku.apply(r,o.args),r=o.func.apply(o.thisArg,r)}return r}function Tr(n,t,r){var e=0,u=n?n.length:e;if(typeof t=="number"&&t===t&&u<=ho){for(;e<u;){var o=e+u>>>1,i=n[o];
(r?i<=t:i<t)?e=o+1:u=o}return u}return Sr(n,t,gu,r)}function Sr(n,t,r,e){t=r(t);for(var u=0,o=n?n.length:0,i=t!==t,a=typeof t=="undefined";u<o;){var f=qu((u+o)/2),c=r(n[f]),l=c===c;(i?l||e:a?l&&(e||typeof c!="undefined"):e?c<=t:c<t)?u=f+1:o=f}return uo(o,po)}function Wr(n,t,r){if(typeof n!="function")return gu;if(typeof t=="undefined")return n;switch(r){case 1:return function(r){return n.call(t,r)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,o){return n.call(t,r,e,u,o)
};case 5:return function(r,e,u,o,i){return n.call(t,r,e,u,o,i)}}return function(){return n.apply(t,arguments)}}function Nr(n){return zu.call(n,0)}function Fr(n,t,r){for(var e=r.length,u=-1,o=eo(n.length-e,0),i=-1,a=t.length,f=mu(o+a);++i<a;)f[i]=t[i];for(;++u<e;)f[r[u]]=n[u];for(;o--;)f[i++]=n[u++];return f}function Ur(n,t,r){for(var e=-1,u=r.length,o=-1,i=eo(n.length-u,0),a=-1,f=t.length,c=mu(i+f);++o<i;)c[o]=n[o];for(i=o;++a<f;)c[i+a]=t[a];for(;++e<u;)c[i+r[e]]=n[o++];return c}function Lr(n,t){return function(r,e,u){var o=t?t():{};
if(e=Xr(e,u,3),To(r)){u=-1;for(var i=r.length;++u<i;){var a=r[u];n(o,a,e(a,u,r),r)}}else or(r,function(t,r,u){n(o,t,e(t,r,u),u)});return o}}function $r(n){return function(){var t=arguments.length,r=arguments[0];if(2>t||null==r)return r;if(3<t&&ee(arguments[1],arguments[2],arguments[3])&&(t=2),3<t&&"function"==typeof arguments[t-2])var e=Wr(arguments[--t-1],arguments[t--],5);else 2<t&&"function"==typeof arguments[t-1]&&(e=arguments[--t]);for(var u=0;++u<t;){var o=arguments[u];o&&n(r,o,e)}return r}
}function Br(n,t){function r(){return(this instanceof r?e:n).apply(t,arguments)}var e=Dr(n);return r}function zr(n){return function(t){var r=-1;t=lu(ou(t));for(var e=t.length,u="";++r<e;)u=n(u,t[r],r);return u}}function Dr(n){return function(){var t=mo(n.prototype),r=n.apply(t,arguments);return Ge(r)?r:t}}function Mr(n,t){return function(r,e,o){o&&ee(r,e,o)&&(e=null);var i=Xr(),a=null==e;if(i===tr&&a||(a=false,e=i(e,o,3)),a){if(e=To(r),e||!Qe(r))return n(e?r:le(r));e=u}return Jr(r,e,t)}}function qr(n,t,r,e,u,o,i,a,f,c){function l(){for(var w=arguments.length,j=w,k=mu(w);j--;)k[j]=arguments[j];
if(e&&(k=Fr(k,e,u)),o&&(k=Ur(k,o,i)),g||y){var j=l.placeholder,E=v(k,j),w=w-E.length;if(w<c){var O=a?zt(a):null,w=eo(c-w,0),C=g?E:null,E=g?null:E,T=g?k:null,k=g?null:k;return t|=g?R:I,t&=~(g?I:R),d||(t&=~(x|A)),k=qr(n,t,r,T,C,k,E,O,f,w),k.placeholder=j,k}}if(j=p?r:this,h&&(n=j[m]),a)for(O=k.length,w=uo(a.length,O),C=zt(k);w--;)E=a[w],k[w]=re(E,O)?C[E]:b;return s&&f<k.length&&(k.length=f),(this instanceof l?_||Dr(n):n).apply(j,k)}var s=t&C,p=t&x,h=t&A,g=t&k,d=t&j,y=t&E,_=!h&&Dr(n),m=n;return l}function Pr(n,t,r){return n=n.length,t=+t,n<t&&to(t)?(t-=n,r=null==r?" ":e(r),fu(r,Du(t/r.length)).slice(0,t)):""
}function Kr(n,t,r,e){function u(){for(var t=-1,a=arguments.length,f=-1,c=e.length,l=mu(a+c);++f<c;)l[f]=e[f];for(;a--;)l[f++]=arguments[++t];return(this instanceof u?i:n).apply(o?r:this,l)}var o=t&x,i=Dr(n);return u}function Vr(n,t,r,e,u,o,i,a){var f=t&A;if(!f&&!Ze(n))throw new Iu($);var c=e?e.length:0;if(c||(t&=~(R|I),e=u=null),c-=u?u.length:0,t&I){var l=e,s=u;e=u=null}var p=!f&&xo(n);if(r=[n,t,r,e,u,l,s,o,i,a],p&&true!==p){e=r[1],t=p[1],a=e|t,o=C|O,u=x|A,i=o|u|j|E;var l=e&C&&!(t&C),s=e&O&&!(t&O),h=(s?r:p)[7],g=(l?r:p)[8];
o=a>=o&&a<=i&&(e<O||(s||l)&&h.length<=g),(!(e>=O&&t>u||e>u&&t>=O)||o)&&(t&x&&(r[2]=p[2],a|=e&x?0:j),(e=p[3])&&(u=r[3],r[3]=u?Fr(u,e,p[4]):zt(e),r[4]=u?v(r[3],B):zt(p[4])),(e=p[5])&&(u=r[5],r[5]=u?Ur(u,e,p[6]):zt(e),r[6]=u?v(r[5],B):zt(p[6])),(e=p[7])&&(r[7]=zt(e)),t&C&&(r[8]=null==r[8]?p[8]:uo(r[8],p[8])),null==r[9]&&(r[9]=p[9]),r[0]=p[0],r[1]=a),t=r[1],a=r[9]}return r[9]=null==a?f?0:n.length:eo(a-c,0)||0,(p?bo:Ao)(t==x?Br(r[0],r[2]):t!=R&&t!=(x|R)||r[4].length?qr.apply(null,r):Kr.apply(null,r),r)
}function Yr(n,t,r,e,u,o,i){var a=-1,f=n.length,c=t.length,l=true;if(f!=c&&(!u||c<=f))return false;for(;l&&++a<f;){var s=n[a],p=t[a],l=b;if(e&&(l=u?e(p,s,a):e(s,p,a)),typeof l=="undefined")if(u)for(var h=c;h--&&(p=t[h],!(l=s&&s===p||r(s,p,e,u,o,i))););else l=s&&s===p||r(s,p,e,u,o,i)}return!!l}function Zr(n,t,r){switch(r){case M:case q:return+n==+t;case P:return n.name==t.name&&n.message==t.message;case V:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case Z:case G:return n==e(t)}return false}function Gr(n,t,r,e,u,o,i){var a=Fo(n),f=a.length,c=Fo(t).length;
if(f!=c&&!u)return false;for(var l,c=-1;++c<f;){var s=a[c],p=Nu.call(t,s);if(p){var h=n[s],g=t[s],p=b;e&&(p=u?e(g,h,s):e(h,g,s)),typeof p=="undefined"&&(p=h&&h===g||r(h,g,e,u,o,i))}if(!p)return false;l||(l="constructor"==s)}return l||(r=n.constructor,e=t.constructor,!(r!=e&&"constructor"in n&&"constructor"in t)||typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)?true:false}function Jr(n,t,r){var e=r?lo:co,u=e,o=u;return or(n,function(n,i,a){i=t(n,i,a),((r?i<u:i>u)||i===e&&i===o)&&(u=i,o=n)
}),o}function Xr(n,t,r){var e=Wt.callback||pu,e=e===pu?tr:e;return r?e(n,t,r):e}function Hr(n,r,e){var u=Wt.indexOf||de,u=u===de?t:u;return n?u(n,r,e):u}function Qr(n){var t=n.length,r=new n.constructor(t);return t&&"string"==typeof n[0]&&Nu.call(n,"index")&&(r.index=n.index,r.input=n.input),r}function ne(n){return n=n.constructor,typeof n=="function"&&n instanceof n||(n=ku),new n}function te(n,t,r){var e=n.constructor;switch(t){case J:return Nr(n);case M:case q:return new e(+n);case X:case H:case Q:case nt:case tt:case rt:case et:case ut:case ot:return t=n.buffer,new e(r?Nr(t):t,n.byteOffset,n.length);
case V:case G:return new e(n);case Z:var u=new e(n.source,yt.exec(n));u.lastIndex=n.lastIndex}return u}function re(n,t){return n=+n,t=null==t?vo:t,-1<n&&0==n%1&&n<t}function ee(n,t,r){if(!Ge(r))return false;var e=typeof t;return"number"==e?(e=r.length,e=ue(e)&&re(t,e)):e="string"==e&&t in n,e&&r[t]===n}function ue(n){return typeof n=="number"&&-1<n&&0==n%1&&n<=vo}function oe(n){return n===n&&(0===n?0<1/n:!Ge(n))}function ie(n,t){n=se(n);for(var r=-1,e=t.length,u={};++r<e;){var o=t[r];o in n&&(u[o]=n[o])
}return u}function ae(n,t){var r={};return hr(n,function(n,e,u){t(n,e,u)&&(r[e]=n)}),r}function fe(n){var t;if(!h(n)||Uu.call(n)!=Y||!(Nu.call(n,"constructor")||(t=n.constructor,typeof t!="function"||t instanceof t)))return false;var r;return hr(n,function(n,t){r=t}),typeof r=="undefined"||Nu.call(n,r)}function ce(n){for(var t=eu(n),r=t.length,e=r&&n.length,u=Wt.support,u=e&&ue(e)&&(To(n)||u.nonEnumArgs&&Ke(n)),o=-1,i=[];++o<r;){var a=t[o];(u&&re(a,e)||Nu.call(n,a))&&i.push(a)}return i}function le(n){return null==n?[]:ue(n.length)?Ge(n)?n:ku(n):uu(n)
}function se(n){return Ge(n)?n:ku(n)}function pe(n,t,r){return n&&n.length?((r?ee(n,t,r):null==t)&&(t=1),Er(n,0>t?0:t)):[]}function he(n,t,r){var e=n?n.length:0;return e?((r?ee(n,t,r):null==t)&&(t=1),t=e-(+t||0),Er(n,0,0>t?0:t)):[]}function ge(n,t,r){var e=-1,u=n?n.length:0;for(t=Xr(t,r,3);++e<u;)if(t(n[e],e,n))return e;return-1}function ve(n){return n?n[0]:b}function de(n,r,e){var u=n?n.length:0;if(!u)return-1;if(typeof e=="number")e=0>e?eo(u+e,0):e||0;else if(e)return e=Tr(n,r),n=n[e],(r===r?r===n:n!==n)?e:-1;
return t(n,r,e)}function ye(n){return pe(n,1)}function _e(n,r,e,u){if(!n||!n.length)return[];typeof r!="boolean"&&null!=r&&(u=e,e=ee(n,r,u)?null:r,r=false);var o=Xr();if((o!==tr||null!=e)&&(e=o(e,u,3)),r&&Hr()==t){r=e;var i;e=-1,u=n.length;for(var o=-1,a=[];++e<u;){var f=n[e],c=r?r(f,e,n):f;e&&i===c||(i=c,a[++o]=f)}n=a}else n=Ir(n,e);return n}function me(n){for(var t=-1,r=(n&&n.length&&Vt(Kt(n,Wu)))>>>0,e=mu(r);++t<r;)e[t]=Kt(n,Ar(t));return e}function be(n,t){var r=-1,e=n?n.length:0,u={};for(!e||t||To(n[0])||(t=[]);++r<e;){var o=n[r];
t?u[o]=t[r]:o&&(u[o[0]]=o[1])}return u}function we(n){return n=Wt(n),n.__chain__=true,n}function xe(n,t,r){return t.call(r,n)}function Ae(n,t,r){var e=n?n.length:0;return ue(e)||(n=uu(n),e=n.length),e?(r=typeof r=="number"?0>r?eo(e+r,0):r||0:0,typeof n=="string"||!To(n)&&Qe(n)?r<e&&-1<n.indexOf(t,r):-1<Hr(n,t,r)):false}function je(n,t,r){var e=To(n)?qt:ar;return(typeof t!="function"||typeof r!="undefined")&&(t=Xr(t,r,3)),e(n,t)}function ke(n,t,r){var e=To(n)?Pt:fr;return t=Xr(t,r,3),e(n,t)}function Ee(n,t,r){return To(n)?(t=ge(n,t,r),-1<t?n[t]:b):(t=Xr(t,r,3),cr(n,t,or))
}function Re(n,t,r){return typeof t=="function"&&typeof r=="undefined"&&To(n)?Mt(n,t):or(n,Wr(t,r,3))}function Ie(n,t,r){if(typeof t=="function"&&typeof r=="undefined"&&To(n))for(r=n.length;r--&&false!==t(n[r],r,n););else n=ir(n,Wr(t,r,3));return n}function Oe(n,t,r){var e=To(n)?Kt:br;return t=Xr(t,r,3),e(n,t)}function Ce(n,t,r,e){return(To(n)?Yt:kr)(n,Xr(t,e,4),r,3>arguments.length,or)}function Te(n,t,r,e){return(To(n)?Zt:kr)(n,Xr(t,e,4),r,3>arguments.length,ir)}function Se(n,t,r){return(r?ee(n,t,r):null==t)?(n=le(n),t=n.length,0<t?n[jr(0,t-1)]:b):(n=We(n),n.length=uo(0>t?0:+t||0,n.length),n)
}function We(n){n=le(n);for(var t=-1,r=n.length,e=mu(r);++t<r;){var u=jr(0,t);t!=u&&(e[t]=e[u]),e[u]=n[t]}return e}function Ne(n,t,r){var e=To(n)?Gt:Rr;return(typeof t!="function"||typeof r!="undefined")&&(t=Xr(t,r,3)),e(n,t)}function Fe(n,t){var r;if(!Ze(t)){if(!Ze(n))throw new Iu($);var e=n;n=t,t=e}return function(){return 0<--n?r=t.apply(this,arguments):t=null,r}}function Ue(n,t){var r=x;if(2<arguments.length)var e=Er(arguments,2),u=v(e,Ue.placeholder),r=r|R;return Vr(n,r,t,e,u)}function Le(n,t){var r=x|A;
if(2<arguments.length)var e=Er(arguments,2),u=v(e,Le.placeholder),r=r|R;return Vr(t,r,n,e,u)}function $e(n,t,r){return r&&ee(n,t,r)&&(t=null),n=Vr(n,k,null,null,null,null,null,t),n.placeholder=$e.placeholder,n}function Be(n,t,r){return r&&ee(n,t,r)&&(t=null),n=Vr(n,E,null,null,null,null,null,t),n.placeholder=Be.placeholder,n}function ze(n,t,r){function e(){var r=t-(Co()-c);0>=r||r>t?(a&&Mu(a),r=p,a=s=p=b,r&&(h=Co(),f=n.apply(l,i),s||a||(i=l=null))):s=Zu(e,r)}function u(){s&&Mu(s),a=s=p=b,(v||g!==t)&&(h=Co(),f=n.apply(l,i),s||a||(i=l=null))
}function o(){if(i=arguments,c=Co(),l=this,p=v&&(s||!d),false===g)var r=d&&!s;else{a||d||(h=c);var o=g-(c-h),y=0>=o||o>g;y?(a&&(a=Mu(a)),h=c,f=n.apply(l,i)):a||(a=Zu(u,o))}return y&&s?s=Mu(s):s||t===g||(s=Zu(e,t)),r&&(y=true,f=n.apply(l,i)),!y||s||a||(i=l=null),f}var i,a,f,c,l,s,p,h=0,g=false,v=true;if(!Ze(n))throw new Iu($);if(t=0>t?0:t,true===r)var d=true,v=false;else Ge(r)&&(d=r.leading,g="maxWait"in r&&eo(+r.maxWait||0,t),v="trailing"in r?r.trailing:v);return o.cancel=function(){s&&Mu(s),a&&Mu(a),a=s=p=b},o}function De(){var n=arguments,t=n.length-1;
if(0>t)return function(){};if(!qt(n,Ze))throw new Iu($);return function(){for(var r=t,e=n[r].apply(this,arguments);r--;)e=n[r].call(this,e);return e}}function Me(n,t){function r(){var e=r.cache,u=t?t.apply(this,arguments):arguments[0];if(e.has(u))return e.get(u);var o=n.apply(this,arguments);return e.set(u,o),o}if(!Ze(n)||t&&!Ze(t))throw new Iu($);return r.cache=new Me.Cache,r}function qe(n){var t=Er(arguments,1),r=v(t,qe.placeholder);return Vr(n,R,null,t,r)}function Pe(n){var t=Er(arguments,1),r=v(t,Pe.placeholder);
return Vr(n,I,null,t,r)}function Ke(n){return ue(h(n)?n.length:b)&&Uu.call(n)==z||false}function Ve(n){return n&&1===n.nodeType&&h(n)&&-1<Uu.call(n).indexOf("Element")||false}function Ye(n){return h(n)&&typeof n.message=="string"&&Uu.call(n)==P||false}function Ze(n){return typeof n=="function"||false}function Ge(n){var t=typeof n;return"function"==t||n&&"object"==t||false}function Je(n){return null==n?false:Uu.call(n)==K?$u.test(Su.call(n)):h(n)&&bt.test(n)||false}function Xe(n){return typeof n=="number"||h(n)&&Uu.call(n)==V||false
}function He(n){return h(n)&&Uu.call(n)==Z||false}function Qe(n){return typeof n=="string"||h(n)&&Uu.call(n)==G||false}function nu(n){return h(n)&&ue(n.length)&&Ct[Uu.call(n)]||false}function tu(n){return nr(n,eu(n))}function ru(n){return dr(n,eu(n))}function eu(n){if(null==n)return[];Ge(n)||(n=ku(n));for(var t=n.length,t=t&&ue(t)&&(To(n)||_o.nonEnumArgs&&Ke(n))&&t||0,r=n.constructor,e=-1,r=typeof r=="function"&&r.prototype==n,u=mu(t),o=0<t;++e<t;)u[e]=e+"";for(var i in n)o&&re(i,t)||"constructor"==i&&(r||!Nu.call(n,i))||u.push(i);
return u}function uu(n){return Or(n,Fo(n))}function ou(n){return(n=e(n))&&n.replace(wt,c)}function iu(n){return(n=e(n))&&jt.test(n)?n.replace(At,"\\$&"):n}function au(n,t,r){return r&&ee(n,t,r)&&(t=0),ao(n,t)}function fu(n,t){var r="";if(n=e(n),t=+t,1>t||!n||!to(t))return r;do t%2&&(r+=n),t=qu(t/2),n+=n;while(t);return r}function cu(n,t,r){var u=n;return(n=e(n))?(r?ee(u,t,r):null==t)?n.slice(d(n),y(n)+1):(t=e(t),n.slice(o(n,t),i(n,t)+1)):n}function lu(n,t,r){return r&&ee(n,t,r)&&(t=null),n=e(n),n.match(t||Rt)||[]
}function su(n){try{return n()}catch(t){return Ye(t)?t:wu(t)}}function pu(n,t,r){return r&&ee(n,t,r)&&(t=null),tr(n,t)}function hu(n){return function(){return n}}function gu(n){return n}function vu(n){return wr(n,true)}function du(n,t,r){if(null==r){var e=Ge(t),u=e&&Fo(t);((u=u&&u.length&&dr(t,u))?u.length:e)||(u=false,r=t,t=n,n=this)}u||(u=dr(t,Fo(t)));var o=true,e=-1,i=Ze(n),a=u.length;false===r?o=false:Ge(r)&&"chain"in r&&(o=r.chain);for(;++e<a;){r=u[e];var f=t[r];n[r]=f,i&&(n.prototype[r]=function(t){return function(){var r=this.__chain__;
if(o||r){var e=n(this.__wrapped__);return(e.__actions__=zt(this.__actions__)).push({func:t,args:arguments,thisArg:n}),e.__chain__=r,e}return r=[this.value()],Ku.apply(r,arguments),t.apply(n,r)}}(f))}return n}function yu(){}function _u(n){return Ar(n+"")}g=g?Dt.defaults($t.Object(),g,Dt.pick($t,Ot)):$t;var mu=g.Array,bu=g.Date,wu=g.Error,xu=g.Function,Au=g.Math,ju=g.Number,ku=g.Object,Eu=g.RegExp,Ru=g.String,Iu=g.TypeError,Ou=mu.prototype,Cu=ku.prototype,Tu=(Tu=g.window)&&Tu.document,Su=xu.prototype.toString,Wu=Ar("length"),Nu=Cu.hasOwnProperty,Fu=0,Uu=Cu.toString,Lu=g._,$u=Eu("^"+iu(Uu).replace(/toString|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),Bu=Je(Bu=g.ArrayBuffer)&&Bu,zu=Je(zu=Bu&&new Bu(0).slice)&&zu,Du=Au.ceil,Mu=g.clearTimeout,qu=Au.floor,Pu=Je(Pu=ku.getPrototypeOf)&&Pu,Ku=Ou.push,Vu=Cu.propertyIsEnumerable,Yu=Je(Yu=g.Set)&&Yu,Zu=g.setTimeout,Gu=Ou.splice,Ju=Je(Ju=g.Uint8Array)&&Ju,Xu=Je(Xu=g.WeakMap)&&Xu,Hu=function(){try{var n=Je(n=g.Float64Array)&&n,t=new n(new Bu(10),0,1)&&n
}catch(r){}return t}(),Qu=Je(Qu=mu.isArray)&&Qu,no=Je(no=ku.create)&&no,to=g.isFinite,ro=Je(ro=ku.keys)&&ro,eo=Au.max,uo=Au.min,oo=Je(oo=bu.now)&&oo,io=Je(io=ju.isFinite)&&io,ao=g.parseInt,fo=Au.random,co=ju.NEGATIVE_INFINITY,lo=ju.POSITIVE_INFINITY,so=Au.pow(2,32)-1,po=so-1,ho=so>>>1,go=Hu?Hu.BYTES_PER_ELEMENT:0,vo=Au.pow(2,53)-1,yo=Xu&&new Xu,_o=Wt.support={};!function(n){_o.funcDecomp=!Je(g.WinRTError)&&kt.test(m),_o.funcNames=typeof xu.name=="string";try{_o.dom=11===Tu.createDocumentFragment().nodeType
}catch(t){_o.dom=false}try{_o.nonEnumArgs=!Vu.call(arguments,1)}catch(r){_o.nonEnumArgs=true}}(0,0),Wt.templateSettings={escape:ht,evaluate:gt,interpolate:vt,variable:"",imports:{_:Wt}};var mo=function(){function n(){}return function(t){if(Ge(t)){n.prototype=t;var r=new n;n.prototype=null}return r||g.Object()}}(),bo=yo?function(n,t){return yo.set(n,t),n}:gu;zu||(Nr=Bu&&Ju?function(n){var t=n.byteLength,r=Hu?qu(t/go):0,e=r*go,u=new Bu(t);if(r){var o=new Hu(u,0,r);o.set(new Hu(n,0,r))}return t!=e&&(o=new Ju(u,e),o.set(new Ju(n,e))),u
}:hu(null));var wo=no&&Yu?function(n){return new Lt(n)}:hu(null),xo=yo?function(n){return yo.get(n)}:yu,Ao=function(){var n=0,t=0;return function(r,e){var u=Co(),o=N-(u-t);if(t=u,0<o){if(++n>=W)return r}else n=0;return bo(r,e)}}(),jo=Lr(function(n,t,r){Nu.call(n,r)?++n[r]:n[r]=1}),ko=Lr(function(n,t,r){Nu.call(n,r)?n[r].push(t):n[r]=[t]}),Eo=Lr(function(n,t,r){n[r]=t}),Ro=Mr(Vt),Io=Mr(function(n){for(var t=-1,r=n.length,e=lo;++t<r;){var u=n[t];u<e&&(e=u)}return e},true),Oo=Lr(function(n,t,r){n[r?0:1].push(t)
},function(){return[[],[]]}),Co=oo||function(){return(new bu).getTime()},To=Qu||function(n){return h(n)&&ue(n.length)&&Uu.call(n)==D||false};_o.dom||(Ve=function(n){return n&&1===n.nodeType&&h(n)&&!Wo(n)||false});var So=io||function(n){return typeof n=="number"&&to(n)};(Ze(/x/)||Ju&&!Ze(Ju))&&(Ze=function(n){return Uu.call(n)==K});var Wo=Pu?function(n){if(!n||Uu.call(n)!=Y)return false;var t=n.valueOf,r=Je(t)&&(r=Pu(t))&&Pu(r);return r?n==r||Pu(n)==r:fe(n)}:fe,No=$r(Ht),Fo=ro?function(n){if(n)var t=n.constructor,r=n.length;
return typeof t=="function"&&t.prototype===n||typeof n!="function"&&r&&ue(r)?ce(n):Ge(n)?ro(n):[]}:ce,Uo=$r(xr),Lo=zr(function(n,t,r){return t=t.toLowerCase(),r?n+t.charAt(0).toUpperCase()+t.slice(1):t}),$o=zr(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()});8!=ao(It+"08")&&(au=function(n,t,r){return(r?ee(n,t,r):null==t)?t=0:t&&(t=+t),n=cu(n),ao(n,t||(mt.test(n)?16:10))});var Bo=zr(function(n,t,r){return n+(r?"_":"")+t.toLowerCase()});return Nt.prototype=Wt.prototype,Ut.prototype["delete"]=function(n){return this.has(n)&&delete this.__data__[n]
},Ut.prototype.get=function(n){return"__proto__"==n?b:this.__data__[n]},Ut.prototype.has=function(n){return"__proto__"!=n&&Nu.call(this.__data__,n)},Ut.prototype.set=function(n,t){return"__proto__"!=n&&(this.__data__[n]=t),this},Lt.prototype.push=function(n){var t=this.data;typeof n=="string"||Ge(n)?t.set.add(n):t.hash[n]=true},Me.Cache=Ut,Wt.after=function(n,t){if(!Ze(t)){if(!Ze(n))throw new Iu($);var r=n;n=t,t=r}return n=to(n=+n)?n:0,function(){return 1>--n?t.apply(this,arguments):void 0}},Wt.ary=function(n,t,r){return r&&ee(n,t,r)&&(t=null),t=n&&null==t?n.length:eo(+t||0,0),Vr(n,C,null,null,null,null,t)
},Wt.assign=No,Wt.at=function(n){return ue(n?n.length:0)&&(n=le(n)),Qt(n,lr(arguments,false,false,1))},Wt.before=Fe,Wt.bind=Ue,Wt.bindAll=function(n){for(var t=n,r=1<arguments.length?lr(arguments,false,false,1):ru(n),e=-1,u=r.length;++e<u;){var o=r[e];t[o]=Vr(t[o],x,t)}return t},Wt.bindKey=Le,Wt.callback=pu,Wt.chain=we,Wt.chunk=function(n,t,r){t=(r?ee(n,t,r):null==t)?1:eo(+t||1,1),r=0;for(var e=n?n.length:0,u=-1,o=mu(Du(e/t));r<e;)o[++u]=Er(n,r,r+=t);return o},Wt.compact=function(n){for(var t=-1,r=n?n.length:0,e=-1,u=[];++t<r;){var o=n[t];
o&&(u[++e]=o)}return u},Wt.constant=hu,Wt.countBy=jo,Wt.create=function(n,t,r){var e=mo(n);return r&&ee(n,t,r)&&(t=null),t?nr(t,e,Fo(t)):e},Wt.curry=$e,Wt.curryRight=Be,Wt.debounce=ze,Wt.defaults=function(n){if(null==n)return n;var t=zt(arguments);return t.push(Jt),No.apply(b,t)},Wt.defer=function(n){return er(n,1,arguments,1)},Wt.delay=function(n,t){return er(n,t,arguments,2)},Wt.difference=function(){for(var n=-1,t=arguments.length;++n<t;){var r=arguments[n];if(To(r)||Ke(r))break}return ur(r,lr(arguments,false,true,++n))
},Wt.drop=pe,Wt.dropRight=he,Wt.dropRightWhile=function(n,t,r){var e=n?n.length:0;if(!e)return[];for(t=Xr(t,r,3);e--&&t(n[e],e,n););return Er(n,0,e+1)},Wt.dropWhile=function(n,t,r){var e=n?n.length:0;if(!e)return[];var u=-1;for(t=Xr(t,r,3);++u<e&&t(n[u],u,n););return Er(n,u)},Wt.filter=ke,Wt.flatten=function(n,t,r){var e=n?n.length:0;return r&&ee(n,t,r)&&(t=false),e?lr(n,t):[]},Wt.flattenDeep=function(n){return n&&n.length?lr(n,true):[]},Wt.flow=function(){var n=arguments,t=n.length;if(!t)return function(){};
if(!qt(n,Ze))throw new Iu($);return function(){for(var r=0,e=n[r].apply(this,arguments);++r<t;)e=n[r].call(this,e);return e}},Wt.flowRight=De,Wt.forEach=Re,Wt.forEachRight=Ie,Wt.forIn=function(n,t,r){return(typeof t!="function"||typeof r!="undefined")&&(t=Wr(t,r,3)),sr(n,t,eu)},Wt.forInRight=function(n,t,r){return t=Wr(t,r,3),pr(n,t,eu)},Wt.forOwn=function(n,t,r){return(typeof t!="function"||typeof r!="undefined")&&(t=Wr(t,r,3)),gr(n,t)},Wt.forOwnRight=function(n,t,r){return t=Wr(t,r,3),pr(n,t,Fo)
},Wt.functions=ru,Wt.groupBy=ko,Wt.indexBy=Eo,Wt.initial=function(n){return he(n,1)},Wt.intersection=function(){for(var n=[],r=-1,e=arguments.length,u=[],o=Hr(),i=o==t;++r<e;){var a=arguments[r];(To(a)||Ke(a))&&(n.push(a),u.push(i&&120<=a.length&&wo(r&&a)))}var e=n.length,i=n[0],f=-1,c=i?i.length:0,l=[],s=u[0];n:for(;++f<c;)if(a=i[f],0>(s?Bt(s,a):o(l,a))){for(r=e;--r;){var p=u[r];if(0>(p?Bt(p,a):o(n[r],a)))continue n}s&&s.push(a),l.push(a)}return l},Wt.invert=function(n,t,r){r&&ee(n,t,r)&&(t=null),r=-1;
for(var e=Fo(n),u=e.length,o={};++r<u;){var i=e[r],a=n[i];t?Nu.call(o,a)?o[a].push(i):o[a]=[i]:o[a]=i}return o},Wt.invoke=function(n,t){return yr(n,t,Er(arguments,2))},Wt.keys=Fo,Wt.keysIn=eu,Wt.map=Oe,Wt.mapValues=function(n,t,r){var e={};return t=Xr(t,r,3),gr(n,function(n,r,u){e[r]=t(n,r,u)}),e},Wt.matches=vu,Wt.memoize=Me,Wt.merge=Uo,Wt.mixin=du,Wt.negate=function(n){if(!Ze(n))throw new Iu($);return function(){return!n.apply(this,arguments)}},Wt.omit=function(n,t,r){if(null==n)return{};if(typeof t!="function"){var e=Kt(lr(arguments,false,false,1),Ru);
return ie(n,ur(eu(n),e))}return t=Wr(t,r,3),ae(n,function(n,r,e){return!t(n,r,e)})},Wt.once=function(n){return Fe(n,2)},Wt.pairs=function(n){for(var t=-1,r=Fo(n),e=r.length,u=mu(e);++t<e;){var o=r[t];u[t]=[o,n[o]]}return u},Wt.partial=qe,Wt.partialRight=Pe,Wt.partition=Oo,Wt.pick=function(n,t,r){return null==n?{}:typeof t=="function"?ae(n,Wr(t,r,3)):ie(n,lr(arguments,false,false,1))},Wt.pluck=function(n,t){return Oe(n,_u(t))},Wt.property=_u,Wt.propertyOf=function(n){return function(t){return null==n?b:n[t]
}},Wt.pull=function(){var n=arguments[0];if(!n||!n.length)return n;for(var t=0,r=Hr(),e=arguments.length;++t<e;)for(var u=0,o=arguments[t];-1<(u=r(n,o,u));)Gu.call(n,u,1);return n},Wt.pullAt=function(t){var r=t||[],e=lr(arguments,false,false,1),u=e.length,o=Qt(r,e);for(e.sort(n);u--;){var i=parseFloat(e[u]);if(i!=a&&re(i)){var a=i;Gu.call(r,i,1)}}return o},Wt.range=function(n,t,r){r&&ee(n,t,r)&&(t=r=null),n=+n||0,r=null==r?1:+r||0,null==t?(t=n,n=0):t=+t||0;var e=-1;t=eo(Du((t-n)/(r||1)),0);for(var u=mu(t);++e<t;)u[e]=n,n+=r;
return u},Wt.rearg=function(n){var t=lr(arguments,false,false,1);return Vr(n,O,null,null,null,t)},Wt.reject=function(n,t,r){var e=To(n)?Pt:fr;return t=Xr(t,r,3),e(n,function(n,r,e){return!t(n,r,e)})},Wt.remove=function(n,t,r){var e=-1,u=n?n.length:0,o=[];for(t=Xr(t,r,3);++e<u;)r=n[e],t(r,e,n)&&(o.push(r),Gu.call(n,e--,1),u--);return o},Wt.rest=ye,Wt.shuffle=We,Wt.slice=function(n,t,r){var e=n?n.length:0;return e?(r&&typeof r!="number"&&ee(n,t,r)&&(t=0,r=e),Er(n,t,r)):[]},Wt.sortBy=function(n,t,e){var u=-1,o=n?n.length:0,i=ue(o)?mu(o):[];
return e&&ee(n,t,e)&&(t=null),t=Xr(t,e,3),or(n,function(n,r,e){i[++u]={a:t(n,r,e),b:u,c:n}}),r(i,a)},Wt.sortByAll=function(n){var t=arguments;3<t.length&&ee(t[1],t[2],t[3])&&(t=[n,t[1]]);var e=-1,u=n?n.length:0,o=lr(t,false,false,1),i=ue(u)?mu(u):[];return or(n,function(n){for(var t=o.length,r=mu(t);t--;)r[t]=null==n?b:n[o[t]];i[++e]={a:r,b:e,c:n}}),r(i,f)},Wt.take=function(n,t,r){return n&&n.length?((r?ee(n,t,r):null==t)&&(t=1),Er(n,0,0>t?0:t)):[]},Wt.takeRight=function(n,t,r){var e=n?n.length:0;return e?((r?ee(n,t,r):null==t)&&(t=1),t=e-(+t||0),Er(n,0>t?0:t)):[]
},Wt.takeRightWhile=function(n,t,r){var e=n?n.length:0;if(!e)return[];for(t=Xr(t,r,3);e--&&t(n[e],e,n););return Er(n,e+1)},Wt.takeWhile=function(n,t,r){var e=n?n.length:0;if(!e)return[];var u=-1;for(t=Xr(t,r,3);++u<e&&t(n[u],u,n););return Er(n,0,u)},Wt.tap=function(n,t,r){return t.call(r,n),n},Wt.throttle=function(n,t,r){var e=true,u=true;if(!Ze(n))throw new Iu($);return false===r?e=false:Ge(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),St.leading=e,St.maxWait=+t,St.trailing=u,ze(n,t,St)
},Wt.thru=xe,Wt.times=function(n,t,r){if(n=+n,1>n||!to(n))return[];var e=-1,u=mu(uo(n,so));for(t=Wr(t,r,1);++e<n;)e<so?u[e]=t(e):t(e);return u},Wt.toArray=function(n){var t=n?n.length:0;return ue(t)?t?zt(n):[]:uu(n)},Wt.toPlainObject=tu,Wt.transform=function(n,t,r,e){var u=To(n)||nu(n);return t=Xr(t,e,4),null==r&&(u||Ge(n)?(e=n.constructor,r=u?To(n)?new e:[]:mo(typeof e=="function"&&e.prototype)):r={}),(u?Mt:gr)(n,function(n,e,u){return t(r,n,e,u)}),r},Wt.union=function(){return Ir(lr(arguments,false,true))
},Wt.uniq=_e,Wt.unzip=me,Wt.values=uu,Wt.valuesIn=function(n){return Or(n,eu(n))},Wt.where=function(n,t){return ke(n,vu(t))},Wt.without=function(n){return ur(n,Er(arguments,1))},Wt.wrap=function(n,t){return t=null==t?gu:t,Vr(t,R,null,[n],[])},Wt.xor=function(){for(var n=-1,t=arguments.length;++n<t;){var r=arguments[n];if(To(r)||Ke(r))var e=e?ur(e,r).concat(ur(r,e)):r}return e?Ir(e):[]},Wt.zip=function(){for(var n=arguments.length,t=mu(n);n--;)t[n]=arguments[n];return me(t)},Wt.zipObject=be,Wt.backflow=De,Wt.collect=Oe,Wt.compose=De,Wt.each=Re,Wt.eachRight=Ie,Wt.extend=No,Wt.iteratee=pu,Wt.methods=ru,Wt.object=be,Wt.select=ke,Wt.tail=ye,Wt.unique=_e,du(Wt,Wt),Wt.attempt=su,Wt.camelCase=Lo,Wt.capitalize=function(n){return(n=e(n))&&n.charAt(0).toUpperCase()+n.slice(1)
},Wt.clone=function(n,t,r,e){return typeof t!="boolean"&&null!=t&&(e=r,r=ee(n,t,e)?null:t,t=false),r=typeof r=="function"&&Wr(r,e,1),rr(n,t,r)},Wt.cloneDeep=function(n,t,r){return t=typeof t=="function"&&Wr(t,r,1),rr(n,true,t)},Wt.deburr=ou,Wt.endsWith=function(n,t,r){n=e(n),t+="";var u=n.length;return r=(typeof r=="undefined"?u:uo(0>r?0:+r||0,u))-t.length,0<=r&&n.indexOf(t,r)==r},Wt.escape=function(n){return(n=e(n))&&pt.test(n)?n.replace(lt,l):n},Wt.escapeRegExp=iu,Wt.every=je,Wt.find=Ee,Wt.findIndex=ge,Wt.findKey=function(n,t,r){return t=Xr(t,r,3),cr(n,t,gr,true)
},Wt.findLast=function(n,t,r){return t=Xr(t,r,3),cr(n,t,ir)},Wt.findLastIndex=function(n,t,r){var e=n?n.length:0;for(t=Xr(t,r,3);e--;)if(t(n[e],e,n))return e;return-1},Wt.findLastKey=function(n,t,r){return t=Xr(t,r,3),cr(n,t,vr,true)},Wt.findWhere=function(n,t){return Ee(n,vu(t))},Wt.first=ve,Wt.has=function(n,t){return n?Nu.call(n,t):false},Wt.identity=gu,Wt.includes=Ae,Wt.indexOf=de,Wt.isArguments=Ke,Wt.isArray=To,Wt.isBoolean=function(n){return true===n||false===n||h(n)&&Uu.call(n)==M||false},Wt.isDate=function(n){return h(n)&&Uu.call(n)==q||false
},Wt.isElement=Ve,Wt.isEmpty=function(n){if(null==n)return true;var t=n.length;return ue(t)&&(To(n)||Qe(n)||Ke(n)||h(n)&&Ze(n.splice))?!t:!Fo(n).length},Wt.isEqual=function(n,t,r,e){return r=typeof r=="function"&&Wr(r,e,3),!r&&oe(n)&&oe(t)?n===t:(e=r?r(n,t):b,typeof e=="undefined"?_r(n,t,r):!!e)},Wt.isError=Ye,Wt.isFinite=So,Wt.isFunction=Ze,Wt.isMatch=function(n,t,r,e){var u=Fo(t),o=u.length;if(r=typeof r=="function"&&Wr(r,e,3),!r&&1==o){var i=u[0];if(e=t[i],oe(e))return null!=n&&e===n[i]&&Nu.call(n,i)
}for(var i=mu(o),a=mu(o);o--;)e=i[o]=t[u[o]],a[o]=oe(e);return mr(n,u,i,a,r)},Wt.isNaN=function(n){return Xe(n)&&n!=+n},Wt.isNative=Je,Wt.isNull=function(n){return null===n},Wt.isNumber=Xe,Wt.isObject=Ge,Wt.isPlainObject=Wo,Wt.isRegExp=He,Wt.isString=Qe,Wt.isTypedArray=nu,Wt.isUndefined=function(n){return typeof n=="undefined"},Wt.kebabCase=$o,Wt.last=function(n){var t=n?n.length:0;return t?n[t-1]:b},Wt.lastIndexOf=function(n,t,r){var e=n?n.length:0;if(!e)return-1;var u=e;if(typeof r=="number")u=(0>r?eo(e+r,0):uo(r||0,e-1))+1;
else if(r)return u=Tr(n,t,true)-1,n=n[u],(t===t?t===n:n!==n)?u:-1;if(t!==t)return p(n,u,true);for(;u--;)if(n[u]===t)return u;return-1},Wt.max=Ro,Wt.min=Io,Wt.noConflict=function(){return g._=Lu,this},Wt.noop=yu,Wt.now=Co,Wt.pad=function(n,t,r){n=e(n),t=+t;var u=n.length;return u<t&&to(t)?(u=(t-u)/2,t=qu(u),u=Du(u),r=Pr("",u,r),r.slice(0,t)+n+r):n},Wt.padLeft=function(n,t,r){return(n=e(n))&&Pr(n,t,r)+n},Wt.padRight=function(n,t,r){return(n=e(n))&&n+Pr(n,t,r)},Wt.parseInt=au,Wt.random=function(n,t,r){r&&ee(n,t,r)&&(t=r=null);
var e=null==n,u=null==t;return null==r&&(u&&typeof n=="boolean"?(r=n,n=1):typeof t=="boolean"&&(r=t,u=true)),e&&u&&(t=1,u=false),n=+n||0,u?(t=n,n=0):t=+t||0,r||n%1||t%1?(r=fo(),uo(n+r*(t-n+parseFloat("1e-"+((r+"").length-1))),t)):jr(n,t)},Wt.reduce=Ce,Wt.reduceRight=Te,Wt.repeat=fu,Wt.result=function(n,t,r){return t=null==n?b:n[t],typeof t=="undefined"&&(t=r),Ze(t)?t.call(n):t},Wt.runInContext=m,Wt.size=function(n){var t=n?n.length:0;return ue(t)?t:Fo(n).length},Wt.snakeCase=Bo,Wt.some=Ne,Wt.sortedIndex=function(n,t,r,e){var u=Xr(r);
return u===tr&&null==r?Tr(n,t):Sr(n,t,u(r,e,1))},Wt.sortedLastIndex=function(n,t,r,e){var u=Xr(r);return u===tr&&null==r?Tr(n,t,true):Sr(n,t,u(r,e,1),true)},Wt.startsWith=function(n,t,r){return n=e(n),r=null==r?0:uo(0>r?0:+r||0,n.length),n.lastIndexOf(t,r)==r},Wt.template=function(n,t,r){var u=Wt.templateSettings;r&&ee(n,t,r)&&(t=r=null),n=e(n),t=Ht(Ht({},r||t),u,Xt),r=Ht(Ht({},t.imports),u.imports,Xt);var o,i,a=Fo(r),f=Or(r,a),c=0;r=t.interpolate||xt;var l="__p+='";r=Eu((t.escape||xt).source+"|"+r.source+"|"+(r===vt?dt:xt).source+"|"+(t.evaluate||xt).source+"|$","g");
var p="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":"";if(n.replace(r,function(t,r,e,u,a,f){return e||(e=u),l+=n.slice(c,f).replace(Et,s),r&&(o=true,l+="'+__e("+r+")+'"),a&&(i=true,l+="';"+a+";\n__p+='"),e&&(l+="'+((__t=("+e+"))==null?'':__t)+'"),c=f+t.length,t}),l+="';",(t=t.variable)||(l="with(obj){"+l+"}"),l=(i?l.replace(it,""):l).replace(at,"$1").replace(ft,"$1;"),l="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(o?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}",t=su(function(){return xu(a,p+"return "+l).apply(b,f)
}),t.source=l,Ye(t))throw t;return t},Wt.trim=cu,Wt.trimLeft=function(n,t,r){var u=n;return(n=e(n))?n.slice((r?ee(u,t,r):null==t)?d(n):o(n,e(t))):n},Wt.trimRight=function(n,t,r){var u=n;return(n=e(n))?(r?ee(u,t,r):null==t)?n.slice(0,y(n)+1):n.slice(0,i(n,e(t))+1):n},Wt.trunc=function(n,t,r){r&&ee(n,t,r)&&(t=null);var u=T;if(r=S,null!=t)if(Ge(t)){var o="separator"in t?t.separator:o,u="length"in t?+t.length||0:u;r="omission"in t?e(t.omission):r}else u=+t||0;if(n=e(n),u>=n.length)return n;if(u-=r.length,1>u)return r;
if(t=n.slice(0,u),null==o)return t+r;if(He(o)){if(n.slice(u).search(o)){var i,a=n.slice(0,u);for(o.global||(o=Eu(o.source,(yt.exec(o)||"")+"g")),o.lastIndex=0;n=o.exec(a);)i=n.index;t=t.slice(0,null==i?u:i)}}else n.indexOf(o,u)!=u&&(o=t.lastIndexOf(o),-1<o&&(t=t.slice(0,o)));return t+r},Wt.unescape=function(n){return(n=e(n))&&st.test(n)?n.replace(ct,_):n},Wt.uniqueId=function(n){var t=++Fu;return e(n)+t},Wt.words=lu,Wt.all=je,Wt.any=Ne,Wt.contains=Ae,Wt.detect=Ee,Wt.foldl=Ce,Wt.foldr=Te,Wt.head=ve,Wt.include=Ae,Wt.inject=Ce,du(Wt,function(){var n={};
return gr(Wt,function(t,r){Wt.prototype[r]||(n[r]=t)}),n}(),false),Wt.sample=Se,Wt.prototype.sample=function(n){return this.__chain__||null!=n?this.thru(function(t){return Se(t,n)}):Se(this.value())},Wt.VERSION=w,Mt("bind bindKey curry curryRight partial partialRight".split(" "),function(n){Wt[n].placeholder=Wt}),Mt(["filter","map","takeWhile"],function(n,t){var r=t==F;Ft.prototype[n]=function(n,e){var u=this.clone(),o=u.filtered,i=u.iteratees||(u.iteratees=[]);return u.filtered=o||r||t==L&&0>u.dir,i.push({iteratee:Xr(n,e,3),type:t}),u
}}),Mt(["drop","take"],function(n,t){var r=n+"Count",e=n+"While";Ft.prototype[n]=function(e){e=null==e?1:eo(+e||0,0);var u=this.clone();if(u.filtered){var o=u[r];u[r]=t?uo(o,e):o+e}else(u.views||(u.views=[])).push({size:e,type:n+(0>u.dir?"Right":"")});return u},Ft.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()},Ft.prototype[n+"RightWhile"]=function(n,t){return this.reverse()[e](n,t).reverse()}}),Mt(["first","last"],function(n,t){var r="take"+(t?"Right":"");Ft.prototype[n]=function(){return this[r](1).value()[0]
}}),Mt(["initial","rest"],function(n,t){var r="drop"+(t?"":"Right");Ft.prototype[n]=function(){return this[r](1)}}),Mt(["pluck","where"],function(n,t){var r=t?"filter":"map",e=t?vu:_u;Ft.prototype[n]=function(n){return this[r](e(n))}}),Ft.prototype.dropWhile=function(n,t){var r,e,u=0>this.dir;return n=Xr(n,t,3),this.filter(function(t,o,i){return r=r&&(u?o<e:o>e),e=o,r||(r=!n(t,o,i))})},Ft.prototype.reject=function(n,t){return n=Xr(n,t,3),this.filter(function(t,r,e){return!n(t,r,e)})},Ft.prototype.slice=function(n,t){n=null==n?0:+n||0;
var r=0>n?this.takeRight(-n):this.drop(n);return typeof t!="undefined"&&(t=+t||0,r=0>t?r.dropRight(-t):r.take(t-n)),r},gr(Ft.prototype,function(n,t){var r=/^(?:first|last)$/.test(t);Wt.prototype[t]=function(){function e(n){return n=[n],Ku.apply(n,o),Wt[t].apply(Wt,n)}var u=this.__wrapped__,o=arguments,i=this.__chain__,a=!!this.__actions__.length,f=u instanceof Ft,c=f&&!a;return r&&!i?c?n.call(u):Wt[t](this.value()):f||To(u)?(u=n.apply(c?u:new Ft(this),o),r||!a&&!u.actions||(u.actions||(u.actions=[])).push({func:xe,args:[e],thisArg:Wt}),new Nt(u,i)):this.thru(e)
}}),Mt("concat join pop push shift sort splice unshift".split(" "),function(n){var t=Ou[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:join|pop|shift)$/.test(n);Wt.prototype[n]=function(){var n=arguments;return e&&!this.__chain__?t.apply(this.value(),n):this[r](function(r){return t.apply(r,n)})}}),Ft.prototype.clone=function(){var n=this.actions,t=this.iteratees,r=this.views,e=new Ft(this.wrapped);return e.actions=n?zt(n):null,e.dir=this.dir,e.dropCount=this.dropCount,e.filtered=this.filtered,e.iteratees=t?zt(t):null,e.takeCount=this.takeCount,e.views=r?zt(r):null,e
},Ft.prototype.reverse=function(){var n=this.filtered,t=n?new Ft(this):this.clone();return t.dir=-1*this.dir,t.filtered=n,t},Ft.prototype.value=function(){var n=this.wrapped.value();if(!To(n))return Cr(n,this.actions);var t,r=this.dir,e=0>r,u=n.length;t=u;for(var o=this.views,i=0,a=-1,f=o?o.length:0;++a<f;){var c=o[a],l=c.size;switch(c.type){case"drop":i+=l;break;case"dropRight":t-=l;break;case"take":t=uo(t,i+l);break;case"takeRight":i=eo(i,t-l)}}t={start:i,end:t},i=t.start,a=t.end,t=this.dropCount,o=uo(a-i,this.takeCount-t),i=e?a:i-1,f=(a=this.iteratees)?a.length:0,c=0,l=[];
n:for(;u--&&c<o;){for(var i=i+r,s=-1,p=n[i];++s<f;){var h=a[s],g=h.iteratee(p,i,n),h=h.type;if(h==U)p=g;else if(!g){if(h==F)continue n;break n}}t?t--:l[c++]=p}return e?l.reverse():l},Wt.prototype.chain=function(){return we(this)},Wt.prototype.reverse=function(){var n=this.__wrapped__;return n instanceof Ft?new Nt(n.reverse()):this.thru(function(n){return n.reverse()})},Wt.prototype.toString=function(){return this.value()+""},Wt.prototype.toJSON=Wt.prototype.valueOf=Wt.prototype.value=function(){return Cr(this.__wrapped__,this.__actions__)
},Wt.prototype.collect=Wt.prototype.map,Wt.prototype.head=Wt.prototype.first,Wt.prototype.select=Wt.prototype.filter,Wt.prototype.tail=Wt.prototype.rest,Wt}var b,w="3.0.0",x=1,A=2,j=4,k=8,E=16,R=32,I=64,O=128,C=256,T=30,S="...",W=150,N=16,F=0,U=1,L=2,$="Expected a function",B="__lodash_placeholder__",z="[object Arguments]",D="[object Array]",M="[object Boolean]",q="[object Date]",P="[object Error]",K="[object Function]",V="[object Number]",Y="[object Object]",Z="[object RegExp]",G="[object String]",J="[object ArrayBuffer]",X="[object Float32Array]",H="[object Float64Array]",Q="[object Int8Array]",nt="[object Int16Array]",tt="[object Int32Array]",rt="[object Uint8Array]",et="[object Uint8ClampedArray]",ut="[object Uint16Array]",ot="[object Uint32Array]",it=/\b__p\+='';/g,at=/\b(__p\+=)''\+/g,ft=/(__e\(.*?\)|\b__t\))\+'';/g,ct=/&(?:amp|lt|gt|quot|#39|#96);/g,lt=/[&<>"'`]/g,st=RegExp(ct.source),pt=RegExp(lt.source),ht=/<%-([\s\S]+?)%>/g,gt=/<%([\s\S]+?)%>/g,vt=/<%=([\s\S]+?)%>/g,dt=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,yt=/\w*$/,_t=/^\s*function[ \n\r\t]+\w/,mt=/^0[xX]/,bt=/^\[object .+?Constructor\]$/,wt=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g,xt=/($^)/,At=/[.*+?^${}()|[\]\/\\]/g,jt=RegExp(At.source),kt=/\bthis\b/,Et=/['\n\r\u2028\u2029\\]/g,Rt=RegExp("[A-Z\\xc0-\\xd6\\xd8-\\xde]{2,}(?=[A-Z\\xc0-\\xd6\\xd8-\\xde][a-z\\xdf-\\xf6\\xf8-\\xff]+)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+|[A-Z\\xc0-\\xd6\\xd8-\\xde]+|[0-9]+","g"),It=" \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",Ot="Array ArrayBuffer Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Math Number Object RegExp Set String _ clearTimeout document isFinite parseInt setTimeout TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap window WinRTError".split(" "),Ct={};
Ct[X]=Ct[H]=Ct[Q]=Ct[nt]=Ct[tt]=Ct[rt]=Ct[et]=Ct[ut]=Ct[ot]=true,Ct[z]=Ct[D]=Ct[J]=Ct[M]=Ct[q]=Ct[P]=Ct[K]=Ct["[object Map]"]=Ct[V]=Ct[Y]=Ct[Z]=Ct["[object Set]"]=Ct[G]=Ct["[object WeakMap]"]=false;var Tt={};Tt[z]=Tt[D]=Tt[J]=Tt[M]=Tt[q]=Tt[X]=Tt[H]=Tt[Q]=Tt[nt]=Tt[tt]=Tt[V]=Tt[Y]=Tt[Z]=Tt[G]=Tt[rt]=Tt[et]=Tt[ut]=Tt[ot]=true,Tt[P]=Tt[K]=Tt["[object Map]"]=Tt["[object Set]"]=Tt["[object WeakMap]"]=false;var St={leading:false,maxWait:0,trailing:false},Wt={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss"},Nt={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","`":"&#96;"},Ft={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#96;":"`"},Ut={"function":true,object:true},Lt={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},$t=Ut[typeof window]&&window!==(this&&this.window)?window:this,Bt=Ut[typeof exports]&&exports&&!exports.nodeType&&exports,Ut=Ut[typeof module]&&module&&!module.nodeType&&module,zt=Bt&&Ut&&typeof global=="object"&&global;
!zt||zt.global!==zt&&zt.window!==zt&&zt.self!==zt||($t=zt);var zt=Ut&&Ut.exports===Bt&&Bt,Dt=m();typeof define=="function"&&typeof define.amd=="object"&&define.amd?($t._=Dt, define(function(){return Dt})):Bt&&Ut?zt?(Ut.exports=Dt)._=Dt:Bt._=Dt:$t._=Dt}).call(this);

2112
static/js/libs/require/require.js

File diff suppressed because it is too large Load Diff

21
static/js/mediastream/api.js

@ -22,9 +22,6 @@ @@ -22,9 +22,6 @@
"use strict";
define(['jquery', 'underscore', 'ua-parser'], function($, _, uaparser) {
var alive_check_timeout = 5000;
var alive_check_timeout_2 = 10000;
var Api = function(version, connector) {
this.version = version;
this.id = null;
@ -48,22 +45,29 @@ define(['jquery', 'underscore', 'ua-parser'], function($, _, uaparser) { @@ -48,22 +45,29 @@ define(['jquery', 'underscore', 'ua-parser'], function($, _, uaparser) {
this.received(data);
}, this));
// Trigger alive heartbeat when nothing is received for a while.
this.heartbeat = window.setInterval(_.bind(function() {
// Heartbeat support.
this.last_receive = null;
this.last_receive_overdue = false;
};
Api.prototype.heartbeat = function(timeout, timeout2) {
// Heartbeat emitter.
var last_receive = this.last_receive;
if (this.connector.connected) {
if (last_receive !== null) {
//console.log("api heartbeat", this.last_receive);
var now = new Date().getTime();
if (this.last_receive_overdue) {
if (now > last_receive + alive_check_timeout_2) {
if (now > last_receive + timeout2) {
console.log("Reconnecting because alive timeout was reached.");
this.last_receive_overdue = false;
this.last_receive = null;
this.connector.disconnect(true);
}
} else {
if (now > last_receive + alive_check_timeout) {
if (now > last_receive + timeout) {
//console.log("overdue 1");
this.last_receive_overdue = true;
this.sendAlive(now);
@ -74,9 +78,6 @@ define(['jquery', 'underscore', 'ua-parser'], function($, _, uaparser) { @@ -74,9 +78,6 @@ define(['jquery', 'underscore', 'ua-parser'], function($, _, uaparser) {
this.last_receive = null;
this.last_receive_overdue = false;
}
}, this), 1000);
this.last_receive = null;
this.last_receive_overdue = false;
};

7
static/js/mediastream/webrtc.js

@ -106,13 +106,6 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u @@ -106,13 +106,6 @@ function($, _, PeerCall, PeerConference, PeerXfer, PeerScreenshare, UserMedia, u
this.api.e.bind("received.offer received.candidate received.answer received.bye received.conference", _.bind(this.processReceived, this));
$(window).on("unload", _.bind(function() {
this.doHangup("unload");
if (this.api.connector) {
this.api.connector.disabled = true;
}
}, this));
// Create default media (audio/video).
this.usermedia = new UserMedia();
this.usermedia.e.on("mediasuccess mediaerror", _.bind(function() {

7
static/js/services/appdata.js

@ -40,8 +40,9 @@ define(["jquery"], function($) { @@ -40,8 +40,9 @@ define(["jquery"], function($) {
// - mainStatus(event, status)
// status (string) : Status id (connected, waiting, ...)
//
// - authorizing(event, flag)
// - authorizing(event, flag, userid)
// flag (bool) : True if authorizing phase, else false.
// userid (string) : User id if a user was authorized.
//
// - userSettingsLoaded(event, loaded, user_settings)
// loaded (bool) : True if something was loaded, else false.
@ -74,13 +75,13 @@ define(["jquery"], function($) { @@ -74,13 +75,13 @@ define(["jquery"], function($) {
service.data = d;
return d;
};
service.authorizing = function(value) {
service.authorizing = function(value, userid) {
// Boolean flag to indicate that an authentication is currently in progress.
if (typeof(value) !== "undefined") {
var v = !!value;
if (v !== service.flags.authorizing) {
service.flags.authorizing = v;
service.e.triggerHandler("authorizing", v);
service.e.triggerHandler("authorizing", v, userid);
}
}
return service.flags.authorizing;

6
static/js/services/buddylist.js

@ -383,6 +383,10 @@ define(['jquery', 'angular', 'underscore', 'modernizr', 'avltree', 'text!partial @@ -383,6 +383,10 @@ define(['jquery', 'angular', 'underscore', 'modernizr', 'avltree', 'text!partial
Buddylist.prototype.setDisplay = function(id, scope, data, queueName) {
var status = data.Status;
if (!status) {
status = {}; // Make sure to show buddies which never set a status.
}
var display = scope.display;
// Set display.name.
display.displayName = status.displayName;
@ -501,7 +505,7 @@ define(['jquery', 'angular', 'underscore', 'modernizr', 'avltree', 'text!partial @@ -501,7 +505,7 @@ define(['jquery', 'angular', 'underscore', 'modernizr', 'avltree', 'text!partial
scope = newscope;
}
}, this));
if (sessionData && sessionData.Status) {
if (sessionData) {
this.setDisplay(id, scope, sessionData, "joined");
} else if (!noApply) {
scope.$apply();

14
static/js/services/rooms.js

@ -34,13 +34,15 @@ define([ @@ -34,13 +34,15 @@ define([
var helloedRoomName = null;
var currentRoom = null;
var randomRoom = null;
var canCreateRooms = !mediaStream.config.AuthorizeRoomCreation;
var canJoinRooms = !mediaStream.config.AuthorizeRoomJoin;
var canCreateRooms = canJoinRooms ? !mediaStream.config.AuthorizeRoomCreation : false;
var joinFailed = function(error) {
setCurrentRoom(null);
switch(error.Code) {
case "default_room_disabled":
priorRoomName = null;
rooms.randomRoom();
break;
case "invalid_credentials":
@ -143,10 +145,11 @@ define([ @@ -143,10 +145,11 @@ define([
appData.e.on("selfReceived", function(event, data) {
_.defer(joinRequestedRoom);
if (mediaStream.config.AuthorizeRoomCreation && !$rootScope.myuserid) {
canCreateRooms = false;
canJoinRooms = (!mediaStream.config.AuthorizeRoomJoin || $rootScope.myuserid) ? true : false
if (canJoinRooms) {
canCreateRooms = (!mediaStream.config.AuthorizeRoomCreation || $rootScope.myuserid) ? true : false;
} else {
canCreateRooms = true;
canCreateRooms = false;
}
});
@ -205,6 +208,9 @@ define([ @@ -205,6 +208,9 @@ define([
canCreateRooms: function() {
return canCreateRooms;
},
canJoinRooms: function() {
return canJoinRooms;
},
joinByName: function(name, replace) {
name = $window.encodeURIComponent(name);
name = name.replace(/^%40/, "@");

10
static/partials/menu.html

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
<span>
<button ng-if="withModule('youtube')" title="{{_('Share a YouTube video')}}" class="btn aenablebtn btn-youtubevideo" ng-show="status=='connected' || status=='conference' || layout.youtubevideo" ng-model="layout.youtubevideo" btn-checkbox><i class="fa fa-youtube"></i></button>
<button ng-if="withModule('presentation')" title="{{_('Share a file as presentation')}}" class="btn aenablebtn btn-presentation" ng-show="status=='connected' || status=='conference' || layout.presentation" ng-model="layout.presentation" btn-checkbox><i class="fa fa-folder-open-o"></i></button>
<button ng-if="withModule('screensharing')" title="{{_('Share your screen')}}" class="btn aenablebtn btn-screenshare" ng-disabled="!supported.screensharing" ng-show="status=='connected' || status=='conference' || layout.screenshare" ng-model="layout.screenshare" btn-checkbox><i class="fa fa-desktop"></i></button>
<button title="{{_('Chat')}}" class="btn btn-chat" ng-class="{messagesunseen: chatMessagesUnseen>0}" ng-model="layout.chat" btn-checkbox btn-checkbox-true="true" btn-checkbox-false="false"><i class="fa fa-comments-o"></i><span class="badge" ng-show="chatMessagesUnseen" ng-bind="chatMessagesUnseen"></span></button>
<button ng-show="myid && myuserid" title="{{_('Contacts')}}" class="btn btn-contacts" ng-click="openContactsManager()"><i class="fa fa-sitemap"></i></button>
<button title="{{_('Mute microphone')}}" class="btn btn-mutemicrophone amutebtn" ng-model="microphoneMute" btn-checkbox btn-checkbox-true="true" btn-checkbox-false="false"><i class="fa"></i></button>
<button title="{{_('Turn camera off')}}" class="btn btn-mutecamera amutebtn" ng-model="cameraMute" btn-checkbox btn-checkbox-true="true" btn-checkbox-false="false"><i class="fa"></i></button>
<button title="{{_('Settings')}}" class="btn btn-settings" ng-model="layout.settings" btn-checkbox btn-checkbox-true="true" btn-checkbox-false="false"><i class="fa fa-cog"></i></button>
</span>

10
static/partials/page/welcome.html

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
<div welcome ng-form="welcome" class="welcome container-fluid" ng-show="!peer">
<div welcome ng-form="welcome" class="welcome container-fluid" ng-show="!peer" ng-controller="UsersettingsController as usersettings">
<div class="welcome-logo"></div>
<div class="welcome-container" ng-controller="UsersettingsController as usersettings">
<div ng-if="canJoinRooms()" class="welcome-container">
<h1>{{_("Enter a room name")}}</h1>
<div class="form-group welcome-input" ng-class="{'has-error': welcome.$invalid, 'has-success': welcome.$valid && roomdata.name}">
<input type="text" class="form-control roomdata-link-input input-lg" ng-model="roomdataInput.name" ng-maxlength="30" on-enter="joinRoomByName(roomdata.name)" placeholder="{{roomdata.placeholder}}">
@ -23,4 +23,10 @@ @@ -23,4 +23,10 @@
<a ng-repeat="room in roomsHistory" ng-click="joinRoomByName(room)"><span class="label label-default">{{room}}</span></a>
</p>
</div>
<div ng-if="!canJoinRooms()" class="welcome-container">
<div class="welcome-login">
<p class="h4">{{_("Please sign in.")}}</p>
<button ng-if="!myuserid && usersettings.loginUserid" type="button" class="btn btn-primary" ng-click="usersettings.loginUserid()">{{_('Sign in')}}</button>
</div>
</div>
</div>

2
static/translation/messages-de.json

File diff suppressed because one or more lines are too long

2
static/translation/messages-ja.json

File diff suppressed because one or more lines are too long

2
static/translation/messages-ko.json

File diff suppressed because one or more lines are too long

2
static/translation/messages-zh-cn.json

File diff suppressed because one or more lines are too long

2
static/translation/messages-zh-tw.json

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save