Merge branch 'master' into webrtc
This commit is contained in:
commit
2fda5fd158
17 changed files with 9794 additions and 204 deletions
|
@ -9,7 +9,8 @@ window.mumbleWebConfig = {
|
|||
'port': true,
|
||||
'token': true,
|
||||
'username': true,
|
||||
'password': true
|
||||
'password': true,
|
||||
'channelName': false
|
||||
},
|
||||
// Default values for user settings
|
||||
// You can see your current value by typing `localStorage.getItem('mumble.$setting')` in the web console.
|
||||
|
|
171
app/index.html
171
app/index.html
|
@ -1,6 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!-- Favicon as generated by realfavicongenerator.net (slightly modified for webpack) -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" href="favicon/favicon-32x32.png" sizes="32x32">
|
||||
|
@ -26,35 +27,52 @@
|
|||
css: { minimal: minimalView }">
|
||||
<!-- ko with: connectDialog -->
|
||||
<div class="connect-dialog dialog" data-bind="visible: visible() && !joinOnly()">
|
||||
<div class="dialog-header">
|
||||
<div id="connect-dialog_title" class="dialog-header">
|
||||
Connect to Server
|
||||
</div>
|
||||
<form data-bind="submit: connect">
|
||||
<table>
|
||||
<tr data-bind="if: $root.config.connectDialog.address">
|
||||
<td>Address</td>
|
||||
<td id="connect-dialog_input_address">Address</td>
|
||||
<td><input id="address" type="text" data-bind="value: address" required></td>
|
||||
</tr>
|
||||
<tr data-bind="if: $root.config.connectDialog.port">
|
||||
<td>Port</td>
|
||||
<td id="connect-dialog_input_port">Port</td>
|
||||
<td><input id="port" type="text" data-bind="value: port" required></td>
|
||||
</tr>
|
||||
<tr data-bind="if: $root.config.connectDialog.token">
|
||||
<td>Token</td>
|
||||
<td><input id="token" type="text" data-bind="value: token"></td>
|
||||
</tr>
|
||||
<tr data-bind="if: $root.config.connectDialog.username">
|
||||
<td>Username</td>
|
||||
<td id="connect-dialog_input_username">Username</td>
|
||||
<td><input id="username" type="text" data-bind="value: username" required></td>
|
||||
</tr>
|
||||
<tr data-bind="if: $root.config.connectDialog.password">
|
||||
<td>Password</td>
|
||||
<td id="connect-dialog_input_password">Password</td>
|
||||
<td><input id="password" type="password" data-bind="value: password"></td>
|
||||
</tr>
|
||||
<tr data-bind="if: $root.config.connectDialog.token">
|
||||
<td id="connect-dialog_input_tokens">Tokens</td>
|
||||
<td>
|
||||
<input type="text" data-bind='value: tokenToAdd, valueUpdate: "afterkeydown"'>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-bind="if: $root.config.connectDialog.token">
|
||||
<td></td>
|
||||
<td>
|
||||
<button id="connect-dialog_controls_remove" class="dialog-submit" type="button" data-bind="enable: selectedTokens().length > 0, click: removeSelectedTokens()">Remove</button>
|
||||
<button id="connect-dialog_controls_add" class="dialog-submit" type="button" data-bind="enable: tokenToAdd().length > 0, click: addToken()">Add</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-bind="if: $root.config.connectDialog.token, visible: tokens().length > 0">
|
||||
<td></td>
|
||||
<td><select id="token" multiple="multiple" height="5" data-bind="options:tokens, selectedOptions:selectedTokens"></select></td>
|
||||
</tr>
|
||||
<tr data-bind="if: $root.config.connectDialog.channelName">
|
||||
<td>Channel</td>
|
||||
<td><input id="channelName" type="text" data-bind="value: channelName"></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="dialog-footer">
|
||||
<input class="dialog-close" type="button" data-bind="click: hide" value="Cancel">
|
||||
<input class="dialog-submit" type="submit" value="Connect">
|
||||
<input id="connect-dialog_controls_cancel" class="dialog-close" type="button" data-bind="click: hide" value="Cancel">
|
||||
<input id="connect-dialog_controls_connect" class="dialog-submit" type="submit" value="Connect">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -76,46 +94,64 @@
|
|||
</div>
|
||||
<form data-bind="submit: connect">
|
||||
<table>
|
||||
<tr>
|
||||
<tr class="reason">
|
||||
<td colspan=2>
|
||||
<!-- ko if: type() == 0 || type() == 8 -->
|
||||
The connection has been refused.
|
||||
<span class="refused">
|
||||
The connection has been refused.
|
||||
</span>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: type() == 1 -->
|
||||
The server uses an incompatible version.
|
||||
<span class="version">
|
||||
The server uses an incompatible version.
|
||||
</span>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: type() == 2 -->
|
||||
Your user name was rejected. Maybe try a different one?
|
||||
<span class="username">
|
||||
Your user name was rejected. Maybe try a different one?
|
||||
</span>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: type() == 3 -->
|
||||
The given password is incorrect.
|
||||
The user name you have chosen requires a special one.
|
||||
<span class="userpassword">
|
||||
The given password is incorrect.
|
||||
The user name you have chosen requires a special one.
|
||||
</span>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: type() == 4 -->
|
||||
The given password is incorrect.
|
||||
<span class="serverpassword">
|
||||
The given password is incorrect.
|
||||
</span>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: type() == 5 -->
|
||||
The user name you have chosen is already in use.
|
||||
<span class="username-in-use">
|
||||
The user name you have chosen is already in use.
|
||||
</span>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: type() == 6 -->
|
||||
The server is full.
|
||||
<span class="full">
|
||||
The server is full.
|
||||
</span>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: type() == 7 -->
|
||||
The server requires you to provide a client certificate
|
||||
which is not supported by this web application.
|
||||
<span class="clientcert">
|
||||
The server requires you to provide a client certificate
|
||||
which is not supported by this web application.
|
||||
</span>
|
||||
<!-- /ko -->
|
||||
<br>
|
||||
The server reports:
|
||||
<span class="server">
|
||||
The server reports:
|
||||
</span>
|
||||
<br>
|
||||
"<span class="connect-error-reason" data-bind="text: reason"></span>"
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-bind="if: type() == 2 || type() == 3 || type() == 5">
|
||||
<td>Username</td>
|
||||
<td class="alternate-username">Username</td>
|
||||
<td><input id="username" type="text" data-bind="value: username" required></td>
|
||||
</tr>
|
||||
<tr data-bind="if: type() == 3 || type() == 4">
|
||||
<td>Password</td>
|
||||
<td class="alternate-password">Password</td>
|
||||
<td><input id="password" type="password" data-bind="value: password" required></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -266,80 +302,97 @@
|
|||
<img class="avatar-view" data-bind="visible: avatarView, attr: { src: avatarView },
|
||||
click: function () { avatarView(null) }"></img>
|
||||
<!-- ko with: userContextMenu -->
|
||||
<ul class="context-menu" data-bind="if: target,
|
||||
<ul class="context-menu user-context-menu" data-bind="if: target,
|
||||
style: { left: posX() + 'px',
|
||||
top: posY() + 'px' }">
|
||||
|
||||
<!-- ko with: target -->
|
||||
<li data-bind="css: { disabled: !canChangeMute() }">
|
||||
<li data-bind="css: { disabled: !canChangeMute() }"
|
||||
class="mute">
|
||||
Mute
|
||||
</li>
|
||||
<li data-bind="css: { disabled: !canChangeDeafen() }">
|
||||
<li data-bind="css: { disabled: !canChangeDeafen() }"
|
||||
class="deafen">
|
||||
Deafen
|
||||
</li>
|
||||
<li data-bind="css: { disabled: !canChangePrioritySpeaker() }">
|
||||
<li data-bind="css: { disabled: !canChangePrioritySpeaker() }"
|
||||
class="priority-speaker">
|
||||
Priority Speaker
|
||||
</li>
|
||||
|
||||
<li data-bind="css: { disabled: !canLocalMute() }">
|
||||
<li data-bind="css: { disabled: !canLocalMute() }"
|
||||
class="local-mute">
|
||||
Local Mute
|
||||
</li>
|
||||
<li data-bind="css: { disabled: !canIgnoreMessages() }">
|
||||
<li data-bind="css: { disabled: !canIgnoreMessages() }"
|
||||
class="ignore-messages">
|
||||
Ignore Messages
|
||||
</li>
|
||||
|
||||
<li data-bind="css: { disabled: !canChangeComment() }, visible: comment">
|
||||
<li data-bind="css: { disabled: !canChangeComment() }, visible: comment"
|
||||
class="view-comment">
|
||||
View Comment
|
||||
</li>
|
||||
<!-- ko if: $data === $root.thisUser() -->
|
||||
<li data-bind="css: { disabled: !canChangeComment() }, visible: true">
|
||||
<li data-bind="css: { disabled: !canChangeComment() }, visible: true"
|
||||
class="change-comment">
|
||||
Change Comment
|
||||
</li>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: $data !== $root.thisUser() -->
|
||||
<li data-bind="css: { disabled: !canChangeComment() }, visible: comment">
|
||||
<li data-bind="css: { disabled: !canChangeComment() }, visible: comment"
|
||||
class="reset-comment">
|
||||
Reset Comment
|
||||
</li>
|
||||
<!-- /ko -->
|
||||
|
||||
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: texture,
|
||||
click: viewAvatar">
|
||||
click: viewAvatar"
|
||||
class="view-avatar">
|
||||
View Avatar
|
||||
</li>
|
||||
<!-- ko if: $data === $root.thisUser() -->
|
||||
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: true,
|
||||
click: changeAvatar">
|
||||
click: changeAvatar"
|
||||
class="change-avatar">
|
||||
Change Avatar
|
||||
</li>
|
||||
<!-- /ko -->
|
||||
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: texture,
|
||||
click: removeAvatar">
|
||||
click: removeAvatar",
|
||||
class="reset-avatar">
|
||||
Reset Avatar
|
||||
</li>
|
||||
|
||||
<li data-bind="css: { disabled: true }, visible: true">
|
||||
<li data-bind="css: { disabled: true }, visible: true"
|
||||
class="send-message">
|
||||
Send Message
|
||||
</li>
|
||||
<li data-bind="css: { disabled: true }, visible: true">
|
||||
<li data-bind="css: { disabled: true }, visible: true"
|
||||
class="information">
|
||||
Information
|
||||
</li>
|
||||
|
||||
<li data-bind="visible: $data === $root.thisUser(),
|
||||
css: { checked: selfMute },
|
||||
click: toggleMute">
|
||||
click: toggleMute"
|
||||
class="self-mute">
|
||||
Self Mute
|
||||
</li>
|
||||
<li data-bind="visible: $data === $root.thisUser(),
|
||||
css: { checked: selfDeaf },
|
||||
click: toggleDeaf">
|
||||
click: toggleDeaf"
|
||||
class="self-deafen">
|
||||
Self Deafen
|
||||
</li>
|
||||
|
||||
<!-- ko if: $data !== $root.thisUser() -->
|
||||
<li data-bind="css: { disabled: true }, visible: true">
|
||||
<li data-bind="css: { disabled: true }, visible: true"
|
||||
class="add-friend">
|
||||
Add Friend
|
||||
</li>
|
||||
<li data-bind="css: { disabled: true }, visible: false">
|
||||
<li data-bind="css: { disabled: true }, visible: false"
|
||||
class="remove-friend">
|
||||
Remove Friend
|
||||
</li>
|
||||
<!-- /ko -->
|
||||
|
@ -348,43 +401,53 @@
|
|||
</ul>
|
||||
<!-- /ko -->
|
||||
<!-- ko with: channelContextMenu -->
|
||||
<ul class="context-menu" data-bind="if: target,
|
||||
<ul class="context-menu channel-context-menu" data-bind="if: target,
|
||||
style: { left: posX() + 'px',
|
||||
top: posY() + 'px' }">
|
||||
|
||||
<!-- ko with: target -->
|
||||
<li data-bind="visible: users.indexOf($root.thisUser()) === -1,
|
||||
css: { disabled: !canJoin() },
|
||||
click: $root.requestMove.bind($root, $root.thisUser())">
|
||||
click: $root.requestMove.bind($root, $root.thisUser())"
|
||||
class="join">
|
||||
Join Channel
|
||||
</li>
|
||||
<li data-bind="css: { disabled: !canAdd() }">
|
||||
<li data-bind="css: { disabled: !canAdd() }"
|
||||
class="add">
|
||||
Add
|
||||
</li>
|
||||
<li data-bind="css: { disabled: !canEdit() }">
|
||||
<li data-bind="css: { disabled: !canEdit() }"
|
||||
class="edit">
|
||||
Edit
|
||||
</li>
|
||||
<li data-bind="css: { disabled: !canRemove() }">
|
||||
<li data-bind="css: { disabled: !canRemove() }"
|
||||
class="remove">
|
||||
Remove
|
||||
</li>
|
||||
|
||||
<li data-bind="css: { disabled: !canLink() }">
|
||||
<li data-bind="css: { disabled: !canLink() }"
|
||||
class="link">
|
||||
Link
|
||||
</li>
|
||||
<li data-bind="css: { disabled: !canUnlink() }">
|
||||
<li data-bind="css: { disabled: !canUnlink() }"
|
||||
class="unlink">
|
||||
Unlink
|
||||
</li>
|
||||
<li data-bind="css: { disabled: !canUnlink() }">
|
||||
<li data-bind="css: { disabled: !canUnlink() }"
|
||||
class="unlink-all">
|
||||
Unlink All
|
||||
</li>
|
||||
|
||||
<li data-bind="css: { disabled: true }">
|
||||
<li data-bind="css: { disabled: true }"
|
||||
class="copy-mumble-url">
|
||||
Copy Mumble URL
|
||||
</li>
|
||||
<li data-bind="css: { disabled: true }">
|
||||
<li data-bind="css: { disabled: true }"
|
||||
class="copy-mumble-web-url">
|
||||
Copy Mumble-Web URL
|
||||
</li>
|
||||
<li data-bind="css: { disabled: !canSendMessage() }">
|
||||
<li data-bind="css: { disabled: !canSendMessage() }"
|
||||
class="send-message">
|
||||
Send Message
|
||||
</li>
|
||||
|
||||
|
|
211
app/index.js
211
app/index.js
|
@ -9,6 +9,7 @@ import _dompurify from 'dompurify'
|
|||
import keyboardjs from 'keyboardjs'
|
||||
|
||||
import { ContinuousVoiceHandler, PushToTalkVoiceHandler, VADVoiceHandler, initVoice } from './voice'
|
||||
import {initialize as localizationInitialize, translate} from './loc';
|
||||
|
||||
const dompurify = _dompurify(window)
|
||||
|
||||
|
@ -50,16 +51,31 @@ function ConnectDialog () {
|
|||
var self = this
|
||||
self.address = ko.observable('')
|
||||
self.port = ko.observable('')
|
||||
self.token = ko.observable('')
|
||||
self.tokenToAdd = ko.observable('')
|
||||
self.selectedTokens = ko.observableArray([])
|
||||
self.tokens = ko.observableArray([])
|
||||
self.username = ko.observable('')
|
||||
self.password = ko.observable('')
|
||||
self.channelName = ko.observable('')
|
||||
self.joinOnly = ko.observable(false)
|
||||
self.visible = ko.observable(true)
|
||||
self.show = self.visible.bind(self.visible, true)
|
||||
self.hide = self.visible.bind(self.visible, false)
|
||||
self.connect = function () {
|
||||
self.hide()
|
||||
ui.connect(self.username(), self.address(), self.port(), self.token(), self.password())
|
||||
ui.connect(self.username(), self.address(), self.port(), self.tokens(), self.password(), self.channelName())
|
||||
}
|
||||
|
||||
self.addToken = function() {
|
||||
if ((self.tokenToAdd() != "") && (self.tokens.indexOf(self.tokenToAdd()) < 0)) {
|
||||
self.tokens.push(self.tokenToAdd())
|
||||
}
|
||||
self.tokenToAdd("")
|
||||
}
|
||||
|
||||
self.removeSelectedTokens = function() {
|
||||
this.tokens.removeAll(this.selectedTokens())
|
||||
this.selectedTokens([])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,7 +345,7 @@ class GlobalBindings {
|
|||
return '[' + new Date().toLocaleTimeString('en-US') + ']'
|
||||
}
|
||||
|
||||
this.connect = (username, host, port, token, password) => {
|
||||
this.connect = (username, host, port, tokens = [], password, channelName = "") => {
|
||||
this.resetClient()
|
||||
|
||||
this.remoteHost(host)
|
||||
|
@ -353,7 +369,8 @@ class GlobalBindings {
|
|||
enabled: true,
|
||||
mic: this._delayedMicNode.stream,
|
||||
audioContext: ctx
|
||||
}
|
||||
},
|
||||
tokens: tokens
|
||||
}).done(client => {
|
||||
log('Connected!')
|
||||
|
||||
|
@ -367,12 +384,18 @@ class GlobalBindings {
|
|||
// Make sure we stay open if we're running as Matrix widget
|
||||
window.matrixWidget.setAlwaysOnScreen(true)
|
||||
|
||||
// Register all channels, recursively
|
||||
const registerChannel = channel => {
|
||||
this._newChannel(channel)
|
||||
channel.children.forEach(registerChannel)
|
||||
// Register all channels, recursively
|
||||
if(channelName.indexOf("/") != 0) {
|
||||
channelName = "/"+channelName;
|
||||
}
|
||||
registerChannel(client.root)
|
||||
const registerChannel = (channel, channelPath) => {
|
||||
this._newChannel(channel)
|
||||
if(channelPath === channelName) {
|
||||
client.self.setChannel(channel)
|
||||
}
|
||||
channel.children.forEach(ch => registerChannel(ch, channelPath+"/"+ch.name))
|
||||
}
|
||||
registerChannel(client.root, "")
|
||||
|
||||
// Register all users
|
||||
client.users.forEach(user => this._newUser(user))
|
||||
|
@ -393,6 +416,14 @@ class GlobalBindings {
|
|||
})
|
||||
})
|
||||
|
||||
// Log permission denied error messages
|
||||
client.on('denied', (type) => {
|
||||
ui.log.push({
|
||||
type: 'generic',
|
||||
value: 'Permission denied : '+ type
|
||||
})
|
||||
})
|
||||
|
||||
// Set own user and root channel
|
||||
this.thisUser(client.self.__ui)
|
||||
this.root(client.root.__ui)
|
||||
|
@ -772,6 +803,26 @@ class GlobalBindings {
|
|||
this.requestMove = (user, channel) => {
|
||||
if (this.connected()) {
|
||||
user.model.setChannel(channel.model)
|
||||
|
||||
let currentUrl = url.parse(document.location.href, true)
|
||||
// delete search param so that query one can be taken into account
|
||||
delete currentUrl.search
|
||||
|
||||
// get full channel path
|
||||
if( channel.parent() ){ // in case this channel is not Root
|
||||
let parent = channel.parent()
|
||||
currentUrl.query.channelName = channel.name()
|
||||
while( parent.parent() ){
|
||||
currentUrl.query.channelName = parent.name() + '/' + currentUrl.query.channelName
|
||||
parent = parent.parent()
|
||||
}
|
||||
} else {
|
||||
// there is no channelName as we moved to Root
|
||||
delete currentUrl.query.channelName
|
||||
}
|
||||
|
||||
// reflect this change in URL
|
||||
window.history.pushState(null, channel.name(), url.format(currentUrl))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -883,7 +934,7 @@ var ui = new GlobalBindings(window.mumbleWebConfig)
|
|||
// Used only for debugging
|
||||
window.mumbleUi = ui
|
||||
|
||||
window.onload = function () {
|
||||
function initializeUI () {
|
||||
var queryParams = url.parse(document.location.href, true).query
|
||||
queryParams = Object.assign({}, window.mumbleWebConfig.defaults, queryParams)
|
||||
var useJoinDialog = queryParams.joinDialog
|
||||
|
@ -901,7 +952,11 @@ window.onload = function () {
|
|||
useJoinDialog = false
|
||||
}
|
||||
if (queryParams.token) {
|
||||
ui.connectDialog.token(queryParams.token)
|
||||
var tokens = queryParams.token
|
||||
if (!Array.isArray(tokens)) {
|
||||
tokens = [tokens]
|
||||
}
|
||||
ui.connectDialog.tokens(tokens)
|
||||
}
|
||||
if (queryParams.username) {
|
||||
ui.connectDialog.username(queryParams.username)
|
||||
|
@ -911,6 +966,9 @@ window.onload = function () {
|
|||
if (queryParams.password) {
|
||||
ui.connectDialog.password(queryParams.password)
|
||||
}
|
||||
if (queryParams.channelName) {
|
||||
ui.connectDialog.channelName(queryParams.channelName)
|
||||
}
|
||||
if (queryParams.avatarurl) {
|
||||
// Download the avatar and upload it to the mumble server when connected
|
||||
let url = queryParams.avatarurl
|
||||
|
@ -939,13 +997,11 @@ window.onload = function () {
|
|||
req.send()
|
||||
}
|
||||
ui.connectDialog.joinOnly(useJoinDialog)
|
||||
userMediaPromise.then(() => {
|
||||
ko.applyBindings(ui)
|
||||
})
|
||||
}
|
||||
ko.applyBindings(ui)
|
||||
|
||||
window.onresize = () => ui.updateSize()
|
||||
ui.updateSize()
|
||||
window.onresize = () => ui.updateSize()
|
||||
ui.updateSize()
|
||||
}
|
||||
|
||||
function log () {
|
||||
console.log.apply(console, arguments)
|
||||
|
@ -996,20 +1052,111 @@ function userToState () {
|
|||
var voiceHandler
|
||||
var testVoiceHandler
|
||||
|
||||
var userMediaPromise = initVoice(data => {
|
||||
if (testVoiceHandler) {
|
||||
testVoiceHandler.write(data)
|
||||
}
|
||||
if (!ui.client) {
|
||||
if (voiceHandler) {
|
||||
voiceHandler.end()
|
||||
/**
|
||||
* @author svartoyg
|
||||
*/
|
||||
function translatePiece(selector, kind, parameters, key) {
|
||||
let element = document.querySelector(selector);
|
||||
if (element !== null) {
|
||||
const translation = translate(key);
|
||||
switch (kind) {
|
||||
default:
|
||||
console.warn('unhandled dom translation kind "' + kind + '"');
|
||||
break;
|
||||
case 'textcontent':
|
||||
element.textContent = translation;
|
||||
break;
|
||||
case 'attribute':
|
||||
element.setAttribute(parameters.name || 'value', translation);
|
||||
break;
|
||||
}
|
||||
voiceHandler = null
|
||||
} else if (voiceHandler) {
|
||||
voiceHandler.write(data)
|
||||
} else {
|
||||
console.warn(`translation selector "${selector}" for "${key}" did not match any element`)
|
||||
}
|
||||
}).then(userMedia => {
|
||||
ui._micStream = userMedia
|
||||
}, err => {
|
||||
window.alert('Failed to initialize user media\nRefresh page to retry.\n' + err)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @author svartoyg
|
||||
*/
|
||||
function translateEverything() {
|
||||
translatePiece('#connect-dialog_title', 'textcontent', {}, 'connectdialog.title');
|
||||
translatePiece('#connect-dialog_input_address', 'textcontent', {}, 'connectdialog.address');
|
||||
translatePiece('#connect-dialog_input_port', 'textcontent', {}, 'connectdialog.port');
|
||||
translatePiece('#connect-dialog_input_username', 'textcontent', {}, 'connectdialog.username');
|
||||
translatePiece('#connect-dialog_input_password', 'textcontent', {}, 'connectdialog.password');
|
||||
translatePiece('#connect-dialog_input_tokens', 'textcontent', {}, 'connectdialog.tokens');
|
||||
translatePiece('#connect-dialog_controls_remove', 'textcontent', {}, 'connectdialog.remove');
|
||||
translatePiece('#connect-dialog_controls_add', 'textcontent', {}, 'connectdialog.add');
|
||||
translatePiece('#connect-dialog_controls_cancel', 'attribute', {'name': 'value'}, 'connectdialog.cancel');
|
||||
translatePiece('#connect-dialog_controls_connect', 'attribute', {'name': 'value'}, 'connectdialog.connect');
|
||||
translatePiece('.connect-dialog.error-dialog .dialog-header', 'textcontent', {}, 'connectdialog.error.title');
|
||||
translatePiece('.connect-dialog.error-dialog .reason .refused', 'textcontent', {}, 'connectdialog.error.reason.refused');
|
||||
translatePiece('.connect-dialog.error-dialog .reason .version', 'textcontent', {}, 'connectdialog.error.reason.version');
|
||||
translatePiece('.connect-dialog.error-dialog .reason .username', 'textcontent', {}, 'connectdialog.error.reason.username');
|
||||
translatePiece('.connect-dialog.error-dialog .reason .userpassword', 'textcontent', {}, 'connectdialog.error.reason.userpassword');
|
||||
translatePiece('.connect-dialog.error-dialog .reason .serverpassword', 'textcontent', {}, 'connectdialog.error.reason.serverpassword');
|
||||
translatePiece('.connect-dialog.error-dialog .reason .username-in-use', 'textcontent', {}, 'connectdialog.error.reason.username_in_use');
|
||||
translatePiece('.connect-dialog.error-dialog .reason .full', 'textcontent', {}, 'connectdialog.error.reason.full');
|
||||
translatePiece('.connect-dialog.error-dialog .reason .clientcert', 'textcontent', {}, 'connectdialog.error.reason.clientcert');
|
||||
translatePiece('.connect-dialog.error-dialog .reason .server', 'textcontent', {}, 'connectdialog.error.reason.server');
|
||||
translatePiece('.connect-dialog.error-dialog .alternate-username', 'textcontent', {}, 'connectdialog.username');
|
||||
translatePiece('.connect-dialog.error-dialog .alternate-password', 'textcontent', {}, 'connectdialog.password');
|
||||
translatePiece('.connect-dialog.error-dialog .dialog-submit', 'attribute', {'name': 'value'}, 'connectdialog.error.retry');
|
||||
translatePiece('.connect-dialog.error-dialog .dialog-close', 'attribute', {'name': 'value'}, 'connectdialog.error.cancel');
|
||||
translatePiece('.join-dialog .dialog-header', 'textcontent', {}, 'joindialog.title');
|
||||
translatePiece('.join-dialog .dialog-submit', 'attribute', {'name': 'value'}, 'joindialog.connect');
|
||||
translatePiece('.user-context-menu .mute', 'textcontent', {}, 'contextmenu.mute');
|
||||
translatePiece('.user-context-menu .deafen', 'textcontent', {}, 'contextmenu.deafen');
|
||||
translatePiece('.user-context-menu .priority-speaker', 'textcontent', {}, 'usercontextmenu.priority_speaker');
|
||||
translatePiece('.user-context-menu .local-mute', 'textcontent', {}, 'usercontextmenu.local_mute');
|
||||
translatePiece('.user-context-menu .ignore-messages', 'textcontent', {}, 'usercontextmenu.ignore_messages');
|
||||
translatePiece('.user-context-menu .view-comment', 'textcontent', {}, 'usercontextmenu.view_comment');
|
||||
translatePiece('.user-context-menu .change-comment', 'textcontent', {}, 'usercontextmenu.change_comment');
|
||||
translatePiece('.user-context-menu .reset-comment', 'textcontent', {}, 'usercontextmenu.reset_comment');
|
||||
translatePiece('.user-context-menu .view-avatar', 'textcontent', {}, 'usercontextmenu.view_avatar');
|
||||
translatePiece('.user-context-menu .change-avatar', 'textcontent', {}, 'usercontextmenu.change_avatar');
|
||||
translatePiece('.user-context-menu .reset-avatar', 'textcontent', {}, 'usercontextmenu.reset_avatar');
|
||||
translatePiece('.user-context-menu .send-message', 'textcontent', {}, 'usercontextmenu.send_message');
|
||||
translatePiece('.user-context-menu .information', 'textcontent', {}, 'usercontextmenu.information');
|
||||
translatePiece('.user-context-menu .self-mute', 'textcontent', {}, 'usercontextmenu.self_mute');
|
||||
translatePiece('.user-context-menu .self-deafen', 'textcontent', {}, 'usercontextmenu.self_deafen');
|
||||
translatePiece('.user-context-menu .add-friend', 'textcontent', {}, 'usercontextmenu.add_friend');
|
||||
translatePiece('.user-context-menu .remove-friend', 'textcontent', {}, 'usercontextmenu.remove_friend');
|
||||
translatePiece('.channel-context-menu .join', 'textcontent', {}, 'channelcontextmenu.join');
|
||||
translatePiece('.channel-context-menu .add', 'textcontent', {}, 'channelcontextmenu.add');
|
||||
translatePiece('.channel-context-menu .edit', 'textcontent', {}, 'channelcontextmenu.edit');
|
||||
translatePiece('.channel-context-menu .remove', 'textcontent', {}, 'channelcontextmenu.remove');
|
||||
translatePiece('.channel-context-menu .link', 'textcontent', {}, 'channelcontextmenu.link');
|
||||
translatePiece('.channel-context-menu .unlink', 'textcontent', {}, 'channelcontextmenu.unlink');
|
||||
translatePiece('.channel-context-menu .unlink-all', 'textcontent', {}, 'channelcontextmenu.unlink_all');
|
||||
translatePiece('.channel-context-menu .copy-mumble-url', 'textcontent', {}, 'channelcontextmenu.copy_mumble_url');
|
||||
translatePiece('.channel-context-menu .copy-mumble-web-url', 'textcontent', {}, 'channelcontextmenu.copy_mumble_web_url');
|
||||
translatePiece('.channel-context-menu .send-message', 'textcontent', {}, 'channelcontextmenu.send_message');
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await localizationInitialize(navigator.language);
|
||||
translateEverything();
|
||||
try {
|
||||
const userMedia = await initVoice(data => {
|
||||
if (testVoiceHandler) {
|
||||
testVoiceHandler.write(data)
|
||||
}
|
||||
if (!ui.client) {
|
||||
if (voiceHandler) {
|
||||
voiceHandler.end()
|
||||
}
|
||||
voiceHandler = null
|
||||
} else if (voiceHandler) {
|
||||
voiceHandler.write(data)
|
||||
}
|
||||
})
|
||||
ui._micStream = userMedia
|
||||
} catch (err) {
|
||||
window.alert('Failed to initialize user media\nRefresh page to retry.\n' + err)
|
||||
return
|
||||
}
|
||||
initializeUI();
|
||||
}
|
||||
|
||||
window.onload = main
|
||||
|
|
99
app/loc.js
Normal file
99
app/loc.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* the default language to use
|
||||
*
|
||||
* @var {string}
|
||||
* @author svartoyg
|
||||
*/
|
||||
var _languageDefault = null;
|
||||
|
||||
|
||||
/**
|
||||
* the fallback language to use
|
||||
*
|
||||
* @var {string}
|
||||
* @author svartoyg
|
||||
*/
|
||||
var _languageFallback = null;
|
||||
|
||||
|
||||
/**
|
||||
* two level map with ISO-639-1 code as first key and translation id as second key
|
||||
*
|
||||
* @var {Map<string,Map<string,string>>}
|
||||
* @author svartoyg
|
||||
*/
|
||||
var _data = {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} language
|
||||
* @return Promise<Map<string,string>>
|
||||
* @author svartoyg
|
||||
*/
|
||||
async function retrieveData (language) {
|
||||
let json
|
||||
try {
|
||||
json = (await import(`../loc/${language}.json`)).default
|
||||
} catch (exception) {
|
||||
json = (await import(`../loc/${language.substr(0, language.indexOf('-'))}.json`)).default
|
||||
}
|
||||
const map = {}
|
||||
flatten(json, '', map)
|
||||
return map
|
||||
}
|
||||
|
||||
function flatten (tree, prefix, result) {
|
||||
for (const [key, value] of Object.entries(tree)) {
|
||||
if (typeof value === 'string') {
|
||||
result[prefix + key] = value
|
||||
} else {
|
||||
flatten(value, prefix + key + '.', result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} languageDefault
|
||||
* @param {string} [languageFallback]
|
||||
* @author svartoyg
|
||||
*/
|
||||
export async function initialize (languageDefault, languageFallback = 'en') {
|
||||
_languageFallback = languageFallback;
|
||||
_languageDefault = languageDefault;
|
||||
for (const language of [_languageFallback, _languageDefault]) {
|
||||
if (_data.hasOwnProperty(language)) continue;
|
||||
console.log('--', 'loading localization data for language "' + language + '" ...');
|
||||
let data;
|
||||
try {
|
||||
data = await retrieveData(language);
|
||||
} catch (exception) {
|
||||
console.warn(exception.toString());
|
||||
}
|
||||
_data[language] = data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* gets a translation by its key for a specific language
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} [languageChosen]
|
||||
* @return {string}
|
||||
* @author svartoyg
|
||||
*/
|
||||
export function translate (key, languageChosen = _languageDefault) {
|
||||
let result = undefined;
|
||||
for (const language of [languageChosen, _languageFallback]) {
|
||||
if (_data.hasOwnProperty(language) && (_data[language] !== undefined) && _data[language].hasOwnProperty(key)) {
|
||||
result = _data[language][key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result === undefined) {
|
||||
result = ('{{' + key + '}}');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -4,8 +4,7 @@ import EventEmitter from 'events'
|
|||
import { Writable, PassThrough } from 'stream'
|
||||
import toArrayBuffer from 'to-arraybuffer'
|
||||
import ByteBuffer from 'bytebuffer'
|
||||
import webworkify from 'webworkify'
|
||||
import worker from './worker'
|
||||
import Worker from './worker'
|
||||
|
||||
/**
|
||||
* Creates proxy MumbleClients to a real ones running on a web worker.
|
||||
|
@ -13,7 +12,7 @@ import worker from './worker'
|
|||
*/
|
||||
class WorkerBasedMumbleConnector {
|
||||
constructor () {
|
||||
this._worker = webworkify(worker)
|
||||
this._worker = new Worker()
|
||||
this._worker.addEventListener('message', this._onMessage.bind(this))
|
||||
this._reqId = 1
|
||||
this._requests = {}
|
||||
|
@ -266,7 +265,7 @@ class WorkerBasedMumbleChannel extends EventEmitter {
|
|||
|
||||
_dispatchEvent (name, args) {
|
||||
if (name === 'update') {
|
||||
let [actor, props] = args
|
||||
let [props] = args
|
||||
Object.entries(props).forEach((entry) => {
|
||||
this._setProp(entry[0], entry[1])
|
||||
})
|
||||
|
@ -277,7 +276,6 @@ class WorkerBasedMumbleChannel extends EventEmitter {
|
|||
props.links = this.links
|
||||
}
|
||||
args = [
|
||||
this._client._user(actor),
|
||||
props
|
||||
]
|
||||
} else if (name === 'remove') {
|
||||
|
|
|
@ -3,18 +3,11 @@ import mumbleConnect from 'mumble-client-websocket'
|
|||
import toArrayBuffer from 'to-arraybuffer'
|
||||
import chunker from 'stream-chunker'
|
||||
import Resampler from 'libsamplerate.js'
|
||||
import CodecsBrowser from 'mumble-client-codecs-browser'
|
||||
|
||||
// Polyfill nested webworkers for https://bugs.chromium.org/p/chromium/issues/detail?id=31666
|
||||
import 'subworkers'
|
||||
|
||||
// Monkey-patch to allow webworkify-webpack and codecs to work inside of web worker
|
||||
/* global URL */
|
||||
window.URL = URL
|
||||
|
||||
// Using require to ensure ordering relative to monkey-patch above
|
||||
let CodecsBrowser = require('mumble-client-codecs-browser')
|
||||
|
||||
export default function (self) {
|
||||
let sampleRate
|
||||
let nextClientId = 1
|
||||
let nextVoiceId = 1
|
||||
|
@ -97,17 +90,14 @@ export default function (self) {
|
|||
function setupChannel (id, channel) {
|
||||
id = Object.assign({}, id, { channel: channel.id })
|
||||
|
||||
registerEventProxy(id, channel, 'update', (actor, props) => {
|
||||
if (actor) {
|
||||
actor = actor.id
|
||||
}
|
||||
registerEventProxy(id, channel, 'update', (props) => {
|
||||
if (props.parent) {
|
||||
props.parent = props.parent.id
|
||||
}
|
||||
if (props.links) {
|
||||
props.links = props.links.map((it) => it.id)
|
||||
}
|
||||
return [actor, props]
|
||||
return [props]
|
||||
})
|
||||
registerEventProxy(id, channel, 'remove')
|
||||
|
||||
|
@ -194,6 +184,7 @@ export default function (self) {
|
|||
id = { client: id }
|
||||
|
||||
registerEventProxy(id, client, 'error')
|
||||
registerEventProxy(id, client, 'denied', it => [it])
|
||||
registerEventProxy(id, client, 'newChannel', (it) => [setupChannel(id, it)])
|
||||
registerEventProxy(id, client, 'newUser', (it) => [setupUser(id, it)])
|
||||
registerEventProxy(id, client, 'message', (sender, message, users, channels, trees) => {
|
||||
|
@ -284,4 +275,5 @@ export default function (self) {
|
|||
console.error('exception during message event', ev.data, ex)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default null
|
Loading…
Add table
Add a link
Reference in a new issue