Merge branch 'master' into webrtc
This commit is contained in:
commit
2fda5fd158
|
@ -23,7 +23,7 @@ RUN cd /home/node && \
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
RUN apk del gcc git make g++
|
RUN apk del gcc git
|
||||||
|
|
||||||
USER node
|
USER node
|
||||||
|
|
||||||
|
@ -31,5 +31,5 @@ EXPOSE 8080
|
||||||
ENV MUMBLE_SERVER=mumble.aventer.biz:64738
|
ENV MUMBLE_SERVER=mumble.aventer.biz:64738
|
||||||
|
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
CMD websockify --ssl-target --web /home/node/dist 8080 "$MUMBLE_SERVER"
|
CMD websockify --ssl-target --web=/home/node/dist 8080 "$MUMBLE_SERVER"
|
||||||
|
|
||||||
|
|
60
README.md
60
README.md
|
@ -25,8 +25,8 @@ or from git (webrtc branch only from git for now):
|
||||||
git clone -b webrtc https://github.com/johni0702/mumble-web
|
git clone -b webrtc https://github.com/johni0702/mumble-web
|
||||||
cd mumble-web
|
cd mumble-web
|
||||||
npm install
|
npm install
|
||||||
npm run build
|
|
||||||
```
|
```
|
||||||
|
Note that npm should not be ran as root, use an unprivileged user account instead.
|
||||||
|
|
||||||
The npm version is prebuilt and ready to use whereas the git version allows you
|
The npm version is prebuilt and ready to use whereas the git version allows you
|
||||||
to e.g. customize the theme before building it.
|
to e.g. customize the theme before building it.
|
||||||
|
@ -41,10 +41,11 @@ Mumble server is running on).
|
||||||
|
|
||||||
Additionally you will need some web server to serve static files and terminate the secure websocket connection (mumble-web-proxy only supports insecure ones).
|
Additionally you will need some web server to serve static files and terminate the secure websocket connection (mumble-web-proxy only supports insecure ones).
|
||||||
|
|
||||||
A sample configuration for nginx that allows access to mumble-web at
|
Here are two web server configuration files (one for [NGINX](https://www.nginx.com/) and one for [Caddy server](https://caddyserver.com/)) which will serve the mumble-web interface at `https://voice.example.com` and allow the websocket to connect at `wss://voice.example.com/demo` (similar to the demo server).
|
||||||
`https://voice.example.com/` and connecting at `wss://voice.example.com/demo`
|
Replace `<proxybox>` with the host name of the machine where `mumble-web-proxy` is running. If `mumble-web-proxy` is running on the same machine as your web server, use `localhost`.
|
||||||
(similar to the demo server) looks like this:
|
|
||||||
```
|
* NGINX configuration file
|
||||||
|
```Nginx
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
server_name voice.example.com;
|
server_name voice.example.com;
|
||||||
|
@ -55,7 +56,7 @@ server {
|
||||||
root /path/to/dist;
|
root /path/to/dist;
|
||||||
}
|
}
|
||||||
location /demo {
|
location /demo {
|
||||||
proxy_pass http://proxybox:64737;
|
proxy_pass http://<proxybox>:64737;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
@ -67,12 +68,55 @@ map $http_upgrade $connection_upgrade {
|
||||||
'' close;
|
'' close;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
where `proxybox` is the machine running mumble-web-proxy (may be `localhost`):
|
|
||||||
|
* Caddy configuration file (`Caddyfile`)
|
||||||
```
|
```
|
||||||
mumble-web-proxy --listen-ws 64737 --server mumbleserver:64738
|
http://voice.example.com {
|
||||||
|
redir https://voice.example.com
|
||||||
|
}
|
||||||
|
|
||||||
|
https://voice.example.com {
|
||||||
|
tls "/etc/letsencrypt/live/voice.example.com/fullchain.pem" "/etc/letsencrypt/live/voice.example.com/privkey.pem"
|
||||||
|
root /path/to/dist
|
||||||
|
proxy /demo http://<proxybox>:64737 {
|
||||||
|
websocket
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To run `mumble-web-proxy`, execute the following command. Replace `<mumbleserver>` with the host name of your Mumble server (the one you connect to using the normal Mumble client).
|
||||||
|
Note that even if your Mumble server is running on the same machine as your `mumble-web-proxy`, you should use the external name because (by default, for disabling see its README) `mumble-web-proxy` will try to verify the certificate provided by the Mumble server and fail if it does not match the given host name.
|
||||||
|
```
|
||||||
|
mumble-web-proxy --listen-ws 64737 --server <mumbleserver>:64738
|
||||||
```
|
```
|
||||||
If your mumble-web-proxy is running behind a NAT or firewall, take note of the respective section in its README.
|
If your mumble-web-proxy is running behind a NAT or firewall, take note of the respective section in its README.
|
||||||
|
|
||||||
|
Make sure that your Mumble server is running. You may now open `https://voice.example.com` in a web browser. You will be prompted for server details: choose either `address: voice.example.com/demo` with `port: 443` or `address: voice.example.com` with `port: 443/demo`. You may prefill these values by appending `?address=voice.example.com/demo&port=443`. Choose a username, and click `Connect`: you should now be able to talk and use the chat.
|
||||||
|
|
||||||
|
Here is an example of systemd service, put it in `/etc/systemd/system/mumble-web.service` and adapt it to your needs:
|
||||||
|
```
|
||||||
|
[Unit]
|
||||||
|
Description=Mumble web interface
|
||||||
|
Documentation=https://github.com/johni0702/mumble-web
|
||||||
|
Requires=network.target mumble-server.service
|
||||||
|
After=network.target mumble-server.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=www-data
|
||||||
|
ExecStart=/usr/bin/websockify --web=/usr/lib/node_modules/mumble-web/dist --ssl-target localhost:64737 localhost:64738
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Then
|
||||||
|
```
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl start mumble-web
|
||||||
|
systemctl enable mumble-web
|
||||||
|
```
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
The `app/config.js` file contains default values and descriptions for all configuration options.
|
The `app/config.js` file contains default values and descriptions for all configuration options.
|
||||||
You can overwrite those by editing the `config.local.js` file within your `dist` folder. Make sure to back up and restore the file whenever you update to a new version.
|
You can overwrite those by editing the `config.local.js` file within your `dist` folder. Make sure to back up and restore the file whenever you update to a new version.
|
||||||
|
|
|
@ -9,7 +9,8 @@ window.mumbleWebConfig = {
|
||||||
'port': true,
|
'port': true,
|
||||||
'token': true,
|
'token': true,
|
||||||
'username': true,
|
'username': true,
|
||||||
'password': true
|
'password': true,
|
||||||
|
'channelName': false
|
||||||
},
|
},
|
||||||
// Default values for user settings
|
// Default values for user settings
|
||||||
// You can see your current value by typing `localStorage.getItem('mumble.$setting')` in the web console.
|
// 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>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
<!-- Favicon as generated by realfavicongenerator.net (slightly modified for webpack) -->
|
<!-- 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="apple-touch-icon" sizes="180x180" href="favicon/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" href="favicon/favicon-32x32.png" sizes="32x32">
|
<link rel="icon" type="image/png" href="favicon/favicon-32x32.png" sizes="32x32">
|
||||||
|
@ -26,35 +27,52 @@
|
||||||
css: { minimal: minimalView }">
|
css: { minimal: minimalView }">
|
||||||
<!-- ko with: connectDialog -->
|
<!-- ko with: connectDialog -->
|
||||||
<div class="connect-dialog dialog" data-bind="visible: visible() && !joinOnly()">
|
<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
|
Connect to Server
|
||||||
</div>
|
</div>
|
||||||
<form data-bind="submit: connect">
|
<form data-bind="submit: connect">
|
||||||
<table>
|
<table>
|
||||||
<tr data-bind="if: $root.config.connectDialog.address">
|
<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>
|
<td><input id="address" type="text" data-bind="value: address" required></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr data-bind="if: $root.config.connectDialog.port">
|
<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>
|
<td><input id="port" type="text" data-bind="value: port" required></td>
|
||||||
</tr>
|
</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">
|
<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>
|
<td><input id="username" type="text" data-bind="value: username" required></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr data-bind="if: $root.config.connectDialog.password">
|
<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>
|
<td><input id="password" type="password" data-bind="value: password"></td>
|
||||||
</tr>
|
</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>
|
</table>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<input class="dialog-close" type="button" data-bind="click: hide" value="Cancel">
|
<input id="connect-dialog_controls_cancel" class="dialog-close" type="button" data-bind="click: hide" value="Cancel">
|
||||||
<input class="dialog-submit" type="submit" value="Connect">
|
<input id="connect-dialog_controls_connect" class="dialog-submit" type="submit" value="Connect">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -76,46 +94,64 @@
|
||||||
</div>
|
</div>
|
||||||
<form data-bind="submit: connect">
|
<form data-bind="submit: connect">
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr class="reason">
|
||||||
<td colspan=2>
|
<td colspan=2>
|
||||||
<!-- ko if: type() == 0 || type() == 8 -->
|
<!-- ko if: type() == 0 || type() == 8 -->
|
||||||
The connection has been refused.
|
<span class="refused">
|
||||||
|
The connection has been refused.
|
||||||
|
</span>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
<!-- ko if: type() == 1 -->
|
<!-- ko if: type() == 1 -->
|
||||||
The server uses an incompatible version.
|
<span class="version">
|
||||||
|
The server uses an incompatible version.
|
||||||
|
</span>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
<!-- ko if: type() == 2 -->
|
<!-- 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 -->
|
||||||
<!-- ko if: type() == 3 -->
|
<!-- ko if: type() == 3 -->
|
||||||
The given password is incorrect.
|
<span class="userpassword">
|
||||||
The user name you have chosen requires a special one.
|
The given password is incorrect.
|
||||||
|
The user name you have chosen requires a special one.
|
||||||
|
</span>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
<!-- ko if: type() == 4 -->
|
<!-- ko if: type() == 4 -->
|
||||||
The given password is incorrect.
|
<span class="serverpassword">
|
||||||
|
The given password is incorrect.
|
||||||
|
</span>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
<!-- ko if: type() == 5 -->
|
<!-- 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 -->
|
||||||
<!-- ko if: type() == 6 -->
|
<!-- ko if: type() == 6 -->
|
||||||
The server is full.
|
<span class="full">
|
||||||
|
The server is full.
|
||||||
|
</span>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
<!-- ko if: type() == 7 -->
|
<!-- ko if: type() == 7 -->
|
||||||
The server requires you to provide a client certificate
|
<span class="clientcert">
|
||||||
which is not supported by this web application.
|
The server requires you to provide a client certificate
|
||||||
|
which is not supported by this web application.
|
||||||
|
</span>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
<br>
|
<br>
|
||||||
The server reports:
|
<span class="server">
|
||||||
|
The server reports:
|
||||||
|
</span>
|
||||||
<br>
|
<br>
|
||||||
"<span class="connect-error-reason" data-bind="text: reason"></span>"
|
"<span class="connect-error-reason" data-bind="text: reason"></span>"
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr data-bind="if: type() == 2 || type() == 3 || type() == 5">
|
<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>
|
<td><input id="username" type="text" data-bind="value: username" required></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr data-bind="if: type() == 3 || type() == 4">
|
<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>
|
<td><input id="password" type="password" data-bind="value: password" required></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -266,80 +302,97 @@
|
||||||
<img class="avatar-view" data-bind="visible: avatarView, attr: { src: avatarView },
|
<img class="avatar-view" data-bind="visible: avatarView, attr: { src: avatarView },
|
||||||
click: function () { avatarView(null) }"></img>
|
click: function () { avatarView(null) }"></img>
|
||||||
<!-- ko with: userContextMenu -->
|
<!-- 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',
|
style: { left: posX() + 'px',
|
||||||
top: posY() + 'px' }">
|
top: posY() + 'px' }">
|
||||||
|
|
||||||
<!-- ko with: target -->
|
<!-- ko with: target -->
|
||||||
<li data-bind="css: { disabled: !canChangeMute() }">
|
<li data-bind="css: { disabled: !canChangeMute() }"
|
||||||
|
class="mute">
|
||||||
Mute
|
Mute
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="css: { disabled: !canChangeDeafen() }">
|
<li data-bind="css: { disabled: !canChangeDeafen() }"
|
||||||
|
class="deafen">
|
||||||
Deafen
|
Deafen
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="css: { disabled: !canChangePrioritySpeaker() }">
|
<li data-bind="css: { disabled: !canChangePrioritySpeaker() }"
|
||||||
|
class="priority-speaker">
|
||||||
Priority Speaker
|
Priority Speaker
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li data-bind="css: { disabled: !canLocalMute() }">
|
<li data-bind="css: { disabled: !canLocalMute() }"
|
||||||
|
class="local-mute">
|
||||||
Local Mute
|
Local Mute
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="css: { disabled: !canIgnoreMessages() }">
|
<li data-bind="css: { disabled: !canIgnoreMessages() }"
|
||||||
|
class="ignore-messages">
|
||||||
Ignore Messages
|
Ignore Messages
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li data-bind="css: { disabled: !canChangeComment() }, visible: comment">
|
<li data-bind="css: { disabled: !canChangeComment() }, visible: comment"
|
||||||
|
class="view-comment">
|
||||||
View Comment
|
View Comment
|
||||||
</li>
|
</li>
|
||||||
<!-- ko if: $data === $root.thisUser() -->
|
<!-- 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
|
Change Comment
|
||||||
</li>
|
</li>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
<!-- ko if: $data !== $root.thisUser() -->
|
<!-- 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
|
Reset Comment
|
||||||
</li>
|
</li>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
|
|
||||||
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: texture,
|
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: texture,
|
||||||
click: viewAvatar">
|
click: viewAvatar"
|
||||||
|
class="view-avatar">
|
||||||
View Avatar
|
View Avatar
|
||||||
</li>
|
</li>
|
||||||
<!-- ko if: $data === $root.thisUser() -->
|
<!-- ko if: $data === $root.thisUser() -->
|
||||||
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: true,
|
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: true,
|
||||||
click: changeAvatar">
|
click: changeAvatar"
|
||||||
|
class="change-avatar">
|
||||||
Change Avatar
|
Change Avatar
|
||||||
</li>
|
</li>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: texture,
|
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: texture,
|
||||||
click: removeAvatar">
|
click: removeAvatar",
|
||||||
|
class="reset-avatar">
|
||||||
Reset Avatar
|
Reset Avatar
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li data-bind="css: { disabled: true }, visible: true">
|
<li data-bind="css: { disabled: true }, visible: true"
|
||||||
|
class="send-message">
|
||||||
Send Message
|
Send Message
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="css: { disabled: true }, visible: true">
|
<li data-bind="css: { disabled: true }, visible: true"
|
||||||
|
class="information">
|
||||||
Information
|
Information
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li data-bind="visible: $data === $root.thisUser(),
|
<li data-bind="visible: $data === $root.thisUser(),
|
||||||
css: { checked: selfMute },
|
css: { checked: selfMute },
|
||||||
click: toggleMute">
|
click: toggleMute"
|
||||||
|
class="self-mute">
|
||||||
Self Mute
|
Self Mute
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="visible: $data === $root.thisUser(),
|
<li data-bind="visible: $data === $root.thisUser(),
|
||||||
css: { checked: selfDeaf },
|
css: { checked: selfDeaf },
|
||||||
click: toggleDeaf">
|
click: toggleDeaf"
|
||||||
|
class="self-deafen">
|
||||||
Self Deafen
|
Self Deafen
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<!-- ko if: $data !== $root.thisUser() -->
|
<!-- 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
|
Add Friend
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="css: { disabled: true }, visible: false">
|
<li data-bind="css: { disabled: true }, visible: false"
|
||||||
|
class="remove-friend">
|
||||||
Remove Friend
|
Remove Friend
|
||||||
</li>
|
</li>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
|
@ -348,43 +401,53 @@
|
||||||
</ul>
|
</ul>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
<!-- ko with: channelContextMenu -->
|
<!-- 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',
|
style: { left: posX() + 'px',
|
||||||
top: posY() + 'px' }">
|
top: posY() + 'px' }">
|
||||||
|
|
||||||
<!-- ko with: target -->
|
<!-- ko with: target -->
|
||||||
<li data-bind="visible: users.indexOf($root.thisUser()) === -1,
|
<li data-bind="visible: users.indexOf($root.thisUser()) === -1,
|
||||||
css: { disabled: !canJoin() },
|
css: { disabled: !canJoin() },
|
||||||
click: $root.requestMove.bind($root, $root.thisUser())">
|
click: $root.requestMove.bind($root, $root.thisUser())"
|
||||||
|
class="join">
|
||||||
Join Channel
|
Join Channel
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="css: { disabled: !canAdd() }">
|
<li data-bind="css: { disabled: !canAdd() }"
|
||||||
|
class="add">
|
||||||
Add
|
Add
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="css: { disabled: !canEdit() }">
|
<li data-bind="css: { disabled: !canEdit() }"
|
||||||
|
class="edit">
|
||||||
Edit
|
Edit
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="css: { disabled: !canRemove() }">
|
<li data-bind="css: { disabled: !canRemove() }"
|
||||||
|
class="remove">
|
||||||
Remove
|
Remove
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li data-bind="css: { disabled: !canLink() }">
|
<li data-bind="css: { disabled: !canLink() }"
|
||||||
|
class="link">
|
||||||
Link
|
Link
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="css: { disabled: !canUnlink() }">
|
<li data-bind="css: { disabled: !canUnlink() }"
|
||||||
|
class="unlink">
|
||||||
Unlink
|
Unlink
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="css: { disabled: !canUnlink() }">
|
<li data-bind="css: { disabled: !canUnlink() }"
|
||||||
|
class="unlink-all">
|
||||||
Unlink All
|
Unlink All
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li data-bind="css: { disabled: true }">
|
<li data-bind="css: { disabled: true }"
|
||||||
|
class="copy-mumble-url">
|
||||||
Copy Mumble URL
|
Copy Mumble URL
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="css: { disabled: true }">
|
<li data-bind="css: { disabled: true }"
|
||||||
|
class="copy-mumble-web-url">
|
||||||
Copy Mumble-Web URL
|
Copy Mumble-Web URL
|
||||||
</li>
|
</li>
|
||||||
<li data-bind="css: { disabled: !canSendMessage() }">
|
<li data-bind="css: { disabled: !canSendMessage() }"
|
||||||
|
class="send-message">
|
||||||
Send Message
|
Send Message
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
211
app/index.js
211
app/index.js
|
@ -9,6 +9,7 @@ import _dompurify from 'dompurify'
|
||||||
import keyboardjs from 'keyboardjs'
|
import keyboardjs from 'keyboardjs'
|
||||||
|
|
||||||
import { ContinuousVoiceHandler, PushToTalkVoiceHandler, VADVoiceHandler, initVoice } from './voice'
|
import { ContinuousVoiceHandler, PushToTalkVoiceHandler, VADVoiceHandler, initVoice } from './voice'
|
||||||
|
import {initialize as localizationInitialize, translate} from './loc';
|
||||||
|
|
||||||
const dompurify = _dompurify(window)
|
const dompurify = _dompurify(window)
|
||||||
|
|
||||||
|
@ -50,16 +51,31 @@ function ConnectDialog () {
|
||||||
var self = this
|
var self = this
|
||||||
self.address = ko.observable('')
|
self.address = ko.observable('')
|
||||||
self.port = 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.username = ko.observable('')
|
||||||
self.password = ko.observable('')
|
self.password = ko.observable('')
|
||||||
|
self.channelName = ko.observable('')
|
||||||
self.joinOnly = ko.observable(false)
|
self.joinOnly = ko.observable(false)
|
||||||
self.visible = ko.observable(true)
|
self.visible = ko.observable(true)
|
||||||
self.show = self.visible.bind(self.visible, true)
|
self.show = self.visible.bind(self.visible, true)
|
||||||
self.hide = self.visible.bind(self.visible, false)
|
self.hide = self.visible.bind(self.visible, false)
|
||||||
self.connect = function () {
|
self.connect = function () {
|
||||||
self.hide()
|
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') + ']'
|
return '[' + new Date().toLocaleTimeString('en-US') + ']'
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connect = (username, host, port, token, password) => {
|
this.connect = (username, host, port, tokens = [], password, channelName = "") => {
|
||||||
this.resetClient()
|
this.resetClient()
|
||||||
|
|
||||||
this.remoteHost(host)
|
this.remoteHost(host)
|
||||||
|
@ -353,7 +369,8 @@ class GlobalBindings {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
mic: this._delayedMicNode.stream,
|
mic: this._delayedMicNode.stream,
|
||||||
audioContext: ctx
|
audioContext: ctx
|
||||||
}
|
},
|
||||||
|
tokens: tokens
|
||||||
}).done(client => {
|
}).done(client => {
|
||||||
log('Connected!')
|
log('Connected!')
|
||||||
|
|
||||||
|
@ -367,12 +384,18 @@ class GlobalBindings {
|
||||||
// Make sure we stay open if we're running as Matrix widget
|
// Make sure we stay open if we're running as Matrix widget
|
||||||
window.matrixWidget.setAlwaysOnScreen(true)
|
window.matrixWidget.setAlwaysOnScreen(true)
|
||||||
|
|
||||||
// Register all channels, recursively
|
// Register all channels, recursively
|
||||||
const registerChannel = channel => {
|
if(channelName.indexOf("/") != 0) {
|
||||||
this._newChannel(channel)
|
channelName = "/"+channelName;
|
||||||
channel.children.forEach(registerChannel)
|
|
||||||
}
|
}
|
||||||
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
|
// Register all users
|
||||||
client.users.forEach(user => this._newUser(user))
|
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
|
// Set own user and root channel
|
||||||
this.thisUser(client.self.__ui)
|
this.thisUser(client.self.__ui)
|
||||||
this.root(client.root.__ui)
|
this.root(client.root.__ui)
|
||||||
|
@ -772,6 +803,26 @@ class GlobalBindings {
|
||||||
this.requestMove = (user, channel) => {
|
this.requestMove = (user, channel) => {
|
||||||
if (this.connected()) {
|
if (this.connected()) {
|
||||||
user.model.setChannel(channel.model)
|
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
|
// Used only for debugging
|
||||||
window.mumbleUi = ui
|
window.mumbleUi = ui
|
||||||
|
|
||||||
window.onload = function () {
|
function initializeUI () {
|
||||||
var queryParams = url.parse(document.location.href, true).query
|
var queryParams = url.parse(document.location.href, true).query
|
||||||
queryParams = Object.assign({}, window.mumbleWebConfig.defaults, queryParams)
|
queryParams = Object.assign({}, window.mumbleWebConfig.defaults, queryParams)
|
||||||
var useJoinDialog = queryParams.joinDialog
|
var useJoinDialog = queryParams.joinDialog
|
||||||
|
@ -901,7 +952,11 @@ window.onload = function () {
|
||||||
useJoinDialog = false
|
useJoinDialog = false
|
||||||
}
|
}
|
||||||
if (queryParams.token) {
|
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) {
|
if (queryParams.username) {
|
||||||
ui.connectDialog.username(queryParams.username)
|
ui.connectDialog.username(queryParams.username)
|
||||||
|
@ -911,6 +966,9 @@ window.onload = function () {
|
||||||
if (queryParams.password) {
|
if (queryParams.password) {
|
||||||
ui.connectDialog.password(queryParams.password)
|
ui.connectDialog.password(queryParams.password)
|
||||||
}
|
}
|
||||||
|
if (queryParams.channelName) {
|
||||||
|
ui.connectDialog.channelName(queryParams.channelName)
|
||||||
|
}
|
||||||
if (queryParams.avatarurl) {
|
if (queryParams.avatarurl) {
|
||||||
// Download the avatar and upload it to the mumble server when connected
|
// Download the avatar and upload it to the mumble server when connected
|
||||||
let url = queryParams.avatarurl
|
let url = queryParams.avatarurl
|
||||||
|
@ -939,13 +997,11 @@ window.onload = function () {
|
||||||
req.send()
|
req.send()
|
||||||
}
|
}
|
||||||
ui.connectDialog.joinOnly(useJoinDialog)
|
ui.connectDialog.joinOnly(useJoinDialog)
|
||||||
userMediaPromise.then(() => {
|
ko.applyBindings(ui)
|
||||||
ko.applyBindings(ui)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onresize = () => ui.updateSize()
|
window.onresize = () => ui.updateSize()
|
||||||
ui.updateSize()
|
ui.updateSize()
|
||||||
|
}
|
||||||
|
|
||||||
function log () {
|
function log () {
|
||||||
console.log.apply(console, arguments)
|
console.log.apply(console, arguments)
|
||||||
|
@ -996,20 +1052,111 @@ function userToState () {
|
||||||
var voiceHandler
|
var voiceHandler
|
||||||
var testVoiceHandler
|
var testVoiceHandler
|
||||||
|
|
||||||
var userMediaPromise = initVoice(data => {
|
/**
|
||||||
if (testVoiceHandler) {
|
* @author svartoyg
|
||||||
testVoiceHandler.write(data)
|
*/
|
||||||
}
|
function translatePiece(selector, kind, parameters, key) {
|
||||||
if (!ui.client) {
|
let element = document.querySelector(selector);
|
||||||
if (voiceHandler) {
|
if (element !== null) {
|
||||||
voiceHandler.end()
|
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 {
|
||||||
} else if (voiceHandler) {
|
console.warn(`translation selector "${selector}" for "${key}" did not match any element`)
|
||||||
voiceHandler.write(data)
|
|
||||||
}
|
}
|
||||||
}).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 { Writable, PassThrough } from 'stream'
|
||||||
import toArrayBuffer from 'to-arraybuffer'
|
import toArrayBuffer from 'to-arraybuffer'
|
||||||
import ByteBuffer from 'bytebuffer'
|
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.
|
* Creates proxy MumbleClients to a real ones running on a web worker.
|
||||||
|
@ -13,7 +12,7 @@ import worker from './worker'
|
||||||
*/
|
*/
|
||||||
class WorkerBasedMumbleConnector {
|
class WorkerBasedMumbleConnector {
|
||||||
constructor () {
|
constructor () {
|
||||||
this._worker = webworkify(worker)
|
this._worker = new Worker()
|
||||||
this._worker.addEventListener('message', this._onMessage.bind(this))
|
this._worker.addEventListener('message', this._onMessage.bind(this))
|
||||||
this._reqId = 1
|
this._reqId = 1
|
||||||
this._requests = {}
|
this._requests = {}
|
||||||
|
@ -266,7 +265,7 @@ class WorkerBasedMumbleChannel extends EventEmitter {
|
||||||
|
|
||||||
_dispatchEvent (name, args) {
|
_dispatchEvent (name, args) {
|
||||||
if (name === 'update') {
|
if (name === 'update') {
|
||||||
let [actor, props] = args
|
let [props] = args
|
||||||
Object.entries(props).forEach((entry) => {
|
Object.entries(props).forEach((entry) => {
|
||||||
this._setProp(entry[0], entry[1])
|
this._setProp(entry[0], entry[1])
|
||||||
})
|
})
|
||||||
|
@ -277,7 +276,6 @@ class WorkerBasedMumbleChannel extends EventEmitter {
|
||||||
props.links = this.links
|
props.links = this.links
|
||||||
}
|
}
|
||||||
args = [
|
args = [
|
||||||
this._client._user(actor),
|
|
||||||
props
|
props
|
||||||
]
|
]
|
||||||
} else if (name === 'remove') {
|
} else if (name === 'remove') {
|
||||||
|
|
|
@ -3,18 +3,11 @@ import mumbleConnect from 'mumble-client-websocket'
|
||||||
import toArrayBuffer from 'to-arraybuffer'
|
import toArrayBuffer from 'to-arraybuffer'
|
||||||
import chunker from 'stream-chunker'
|
import chunker from 'stream-chunker'
|
||||||
import Resampler from 'libsamplerate.js'
|
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
|
// Polyfill nested webworkers for https://bugs.chromium.org/p/chromium/issues/detail?id=31666
|
||||||
import 'subworkers'
|
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 sampleRate
|
||||||
let nextClientId = 1
|
let nextClientId = 1
|
||||||
let nextVoiceId = 1
|
let nextVoiceId = 1
|
||||||
|
@ -97,17 +90,14 @@ export default function (self) {
|
||||||
function setupChannel (id, channel) {
|
function setupChannel (id, channel) {
|
||||||
id = Object.assign({}, id, { channel: channel.id })
|
id = Object.assign({}, id, { channel: channel.id })
|
||||||
|
|
||||||
registerEventProxy(id, channel, 'update', (actor, props) => {
|
registerEventProxy(id, channel, 'update', (props) => {
|
||||||
if (actor) {
|
|
||||||
actor = actor.id
|
|
||||||
}
|
|
||||||
if (props.parent) {
|
if (props.parent) {
|
||||||
props.parent = props.parent.id
|
props.parent = props.parent.id
|
||||||
}
|
}
|
||||||
if (props.links) {
|
if (props.links) {
|
||||||
props.links = props.links.map((it) => it.id)
|
props.links = props.links.map((it) => it.id)
|
||||||
}
|
}
|
||||||
return [actor, props]
|
return [props]
|
||||||
})
|
})
|
||||||
registerEventProxy(id, channel, 'remove')
|
registerEventProxy(id, channel, 'remove')
|
||||||
|
|
||||||
|
@ -194,6 +184,7 @@ export default function (self) {
|
||||||
id = { client: id }
|
id = { client: id }
|
||||||
|
|
||||||
registerEventProxy(id, client, 'error')
|
registerEventProxy(id, client, 'error')
|
||||||
|
registerEventProxy(id, client, 'denied', it => [it])
|
||||||
registerEventProxy(id, client, 'newChannel', (it) => [setupChannel(id, it)])
|
registerEventProxy(id, client, 'newChannel', (it) => [setupChannel(id, it)])
|
||||||
registerEventProxy(id, client, 'newUser', (it) => [setupUser(id, it)])
|
registerEventProxy(id, client, 'newUser', (it) => [setupUser(id, it)])
|
||||||
registerEventProxy(id, client, 'message', (sender, message, users, channels, trees) => {
|
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)
|
console.error('exception during message event', ev.data, ex)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
export default null
|
15
loc/de.json
Normal file
15
loc/de.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"connectdialog": {
|
||||||
|
"title": "Verbindung herstellen",
|
||||||
|
"address": "Adresse",
|
||||||
|
"port": "Port",
|
||||||
|
"username": "Nutzername",
|
||||||
|
"password": "Passwort",
|
||||||
|
"tokens": "Tokens",
|
||||||
|
"remove": "Entfernen",
|
||||||
|
"add": "Hinzufügen",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"connect": "Verbinden"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
66
loc/en.json
Normal file
66
loc/en.json
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
{
|
||||||
|
"connectdialog": {
|
||||||
|
"title": "Connect to Server",
|
||||||
|
"address": "Address",
|
||||||
|
"port": "Port",
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"tokens": "Tokens",
|
||||||
|
"remove": "Remove",
|
||||||
|
"add": "Add",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"connect": "Connect",
|
||||||
|
"error": {
|
||||||
|
"title": "Failed to connect",
|
||||||
|
"reason": {
|
||||||
|
"refused": "The connection has been refused.",
|
||||||
|
"version": "The server uses an incompatible version.",
|
||||||
|
"username": "Your user name was rejected. Maybe try a different one?",
|
||||||
|
"userpassword": "The given password is incorrect.\nThe user name you have chosen requires a special one.",
|
||||||
|
"serverpassword": "The given password is incorrect.",
|
||||||
|
"username_in_use": "The user name you have chosen is already in use.",
|
||||||
|
"full": "The server is full.",
|
||||||
|
"clientcert": "The server requires you to provide a client certificate which is not supported by this web application.",
|
||||||
|
"server": "The server reports:"
|
||||||
|
},
|
||||||
|
"retry": "Retry",
|
||||||
|
"cancel": "Cancel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"joindialog": {
|
||||||
|
"title": "Mumble Voice Conference",
|
||||||
|
"connect": "Join Conference"
|
||||||
|
},
|
||||||
|
"usercontextmenu": {
|
||||||
|
"mute": "Mute",
|
||||||
|
"deafen": "Deafen",
|
||||||
|
"priority_speaker": "Priority Speaker",
|
||||||
|
"local_mute": "Local Mute",
|
||||||
|
"ignore_messages": "Ignore Messages",
|
||||||
|
"view_comment": "View Comment",
|
||||||
|
"change_comment": "Change Comment",
|
||||||
|
"reset_comment": "Reset Comment",
|
||||||
|
"view_avatar": "View Avatar",
|
||||||
|
"change_avatar": "Change Avatar",
|
||||||
|
"reset_avatar": "Reset Avatar",
|
||||||
|
"send_message": "Send Message",
|
||||||
|
"information": "Information",
|
||||||
|
"self_mute": "Self Mute",
|
||||||
|
"self_deafen": "Self Deafen",
|
||||||
|
"add_friend": "Add Friend",
|
||||||
|
"remove_friend": "Remove Friend"
|
||||||
|
},
|
||||||
|
"channelcontextmenu": {
|
||||||
|
"channelcontextmenu.join": "Join Channel",
|
||||||
|
"channelcontextmenu.add": "Add",
|
||||||
|
"channelcontextmenu.edit": "Edit",
|
||||||
|
"channelcontextmenu.remove": "Remove",
|
||||||
|
"channelcontextmenu.link": "Link",
|
||||||
|
"channelcontextmenu.unlink": "Unlink",
|
||||||
|
"channelcontextmenu.unlink_all": "Unlink All",
|
||||||
|
"channelcontextmenu.copy_mumble_url": "Copy Mumble URL",
|
||||||
|
"channelcontextmenu.copy_mumble_web_url": "Copy Mumble-Web URL",
|
||||||
|
"channelcontextmenu.send_message": "Send Message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
15
loc/eo.json
Normal file
15
loc/eo.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"connectdialog": {
|
||||||
|
"title": "Konektado",
|
||||||
|
"address": "Adreso",
|
||||||
|
"port": "Pordo",
|
||||||
|
"username": "Uzantnomo",
|
||||||
|
"password": "Pasvorto",
|
||||||
|
"tokens": "Ĵetonoj",
|
||||||
|
"remove": "Forigi",
|
||||||
|
"add": "Aldoni",
|
||||||
|
"cancel": "Nuligi",
|
||||||
|
"connect": "Konekti"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
65
oc.json
Normal file
65
oc.json
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
{
|
||||||
|
"connectdialog": {
|
||||||
|
"title": "Connexion al servidor",
|
||||||
|
"address": "Adreça",
|
||||||
|
"port": "Pòrt",
|
||||||
|
"username": "Nom d’utilizaire",
|
||||||
|
"password": "Senhal",
|
||||||
|
"tokens": "Getons",
|
||||||
|
"remove": "Suprimir",
|
||||||
|
"add": "Ajustar",
|
||||||
|
"cancel": "Anullar",
|
||||||
|
"connect": "Se connectar",
|
||||||
|
"error": {
|
||||||
|
"title": "Connexion impossibla",
|
||||||
|
"reason": {
|
||||||
|
"refused": "Lo servidor a refusat la connexion.",
|
||||||
|
"version": "Lo servidor utiliza una version incompatibla.",
|
||||||
|
"username": "Vòstre nom d’utilizaire es estat regetat. Ensajatz benlèu un autre ?",
|
||||||
|
"userpassword": "Lo senhal donat es incorrèct.\nLo nom d’utilizaire qu’avètz causit requerís un senhal especial.",
|
||||||
|
"serverpassword": "Lo senhal donat es incorrèct.",
|
||||||
|
"username_in_use": "Lo nom d’utilizaire donat es ja utilizat.",
|
||||||
|
"full": "Lo servidor es plen.",
|
||||||
|
"clientcert": "Lo servidor requerís que forniscatz un certificat client qu’es pas compatible amb aquesta web aplicacion.",
|
||||||
|
"server": "Lo servidor senhala :"
|
||||||
|
},
|
||||||
|
"retry": "Ensajar tornamai",
|
||||||
|
"cancel": "Anullar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"joindialog": {
|
||||||
|
"title": "Conferéncia àudio Mumble",
|
||||||
|
"connect": "Participar a la conferéncia"
|
||||||
|
},
|
||||||
|
"usercontextmenu": {
|
||||||
|
"mute": "Copar lo son",
|
||||||
|
"deafen": "Sordina",
|
||||||
|
"priority_speaker": "Prioritat parlaire",
|
||||||
|
"local_mute": "Copar lo son localament",
|
||||||
|
"ignore_messages": "Ignorar los messatges",
|
||||||
|
"view_comment": "Veire lo comentari",
|
||||||
|
"change_comment": "Cambiar lo comentari",
|
||||||
|
"reset_comment": "Escafar lo comentari",
|
||||||
|
"view_avatar": "Veire l’avatar",
|
||||||
|
"change_avatar": "Cambiar l’avatar",
|
||||||
|
"reset_avatar": "Escafar Avatar",
|
||||||
|
"send_message": "Enviar un messatge",
|
||||||
|
"information": "Informacions",
|
||||||
|
"self_mute": "Copar mon son",
|
||||||
|
"self_deafen": "Me metre en sordina",
|
||||||
|
"add_friend": "Ajustar coma amic",
|
||||||
|
"remove_friend": "Tirar dels amics"
|
||||||
|
},
|
||||||
|
"channelcontextmenu": {
|
||||||
|
"channelcontextmenu.join": "Rejónher la sala",
|
||||||
|
"channelcontextmenu.add": "Ajustar",
|
||||||
|
"channelcontextmenu.edit": "Modificar",
|
||||||
|
"channelcontextmenu.remove": "Suprimir",
|
||||||
|
"channelcontextmenu.link": "Associar",
|
||||||
|
"channelcontextmenu.unlink": "Desassociar",
|
||||||
|
"channelcontextmenu.unlink_all": "Tot desassociar",
|
||||||
|
"channelcontextmenu.copy_mumble_url": "Copair l’URL Mumble",
|
||||||
|
"channelcontextmenu.copy_mumble_web_url": "Copiar l’URL Mumble-Web",
|
||||||
|
"channelcontextmenu.send_message": "Enviar messatge"
|
||||||
|
}
|
||||||
|
}
|
8895
package-lock.json
generated
Normal file
8895
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
57
package.json
57
package.json
|
@ -4,7 +4,8 @@
|
||||||
"description": "An HTML5 Mumble client.",
|
"description": "An HTML5 Mumble client.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack && [ -f dist/config.local.js ] || cp app/config.local.js dist/",
|
"build": "webpack && [ -f dist/config.local.js ] || cp app/config.local.js dist/",
|
||||||
"prepublish": "rm -rf dist && npm run build",
|
"watch": "webpack --watch",
|
||||||
|
"prepare": "rm -rf dist && npm run build",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "Jonas Herzig <me@johni0702.de>",
|
"author": "Jonas Herzig <me@johni0702.de>",
|
||||||
|
@ -15,37 +16,41 @@
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"audio-buffer-utils": "^3.1.2",
|
"@babel/core": "^7.9.0",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.9.0",
|
||||||
|
"@babel/preset-env": "^7.9.0",
|
||||||
|
"@babel/runtime": "^7.9.2",
|
||||||
|
"audio-buffer-utils": "^5.1.2",
|
||||||
"audio-context": "^1.0.3",
|
"audio-context": "^1.0.3",
|
||||||
"babel-core": "^6.18.2",
|
"babel-loader": "^8.1.0",
|
||||||
"babel-loader": "^6.2.8",
|
"brfs": "^2.0.2",
|
||||||
"babel-plugin-transform-runtime": "^6.15.0",
|
|
||||||
"babel-preset-es2015": "^6.14.0",
|
|
||||||
"babel-runtime": "^6.18.0",
|
|
||||||
"brfs": "^1.4.3",
|
|
||||||
"bytebuffer": "^5.0.1",
|
"bytebuffer": "^5.0.1",
|
||||||
"css-loader": "^0.26.0",
|
"css-loader": "^3.4.2",
|
||||||
"dompurify": "^0.8.9",
|
"dompurify": "^2.0.8",
|
||||||
"drop-stream": "^1.0.0",
|
"drop-stream": "^1.0.0",
|
||||||
"duplex-maker": "^1.0.0",
|
"duplex-maker": "^1.0.0",
|
||||||
"extract-loader": "^0.1.0",
|
"extract-loader": "^5.0.1",
|
||||||
"file-loader": "^0.9.0",
|
"file-loader": "^4.3.0",
|
||||||
"html-loader": "^0.4.4",
|
"fs": "0.0.1-security",
|
||||||
"json-loader": "^0.5.4",
|
"html-loader": "^0.5.5",
|
||||||
"keyboardjs": "^2.3.4",
|
"json-loader": "^0.5.7",
|
||||||
"knockout": "^3.4.0",
|
"keyboardjs": "^2.5.1",
|
||||||
|
"knockout": "^3.5.1",
|
||||||
"lodash.assign": "^4.2.0",
|
"lodash.assign": "^4.2.0",
|
||||||
"microphone-stream": "^3.0.5",
|
"microphone-stream": "^5.0.1",
|
||||||
"node-sass": "^4.9.3",
|
"mumble-client": "github:johni0702/mumble-client#ddf8424",
|
||||||
"raw-loader": "^0.5.1",
|
"mumble-client-websocket": "^1.0.0",
|
||||||
"regexp-replace-loader": "0.0.1",
|
"node-sass": "^4.13.1",
|
||||||
"sass-loader": "^4.1.1",
|
"raw-loader": "^4.0.0",
|
||||||
|
"regexp-replace-loader": "1.0.1",
|
||||||
|
"sass-loader": "^8.0.2",
|
||||||
"stream-chunker": "^1.2.8",
|
"stream-chunker": "^1.2.8",
|
||||||
"to-arraybuffer": "^1.0.1",
|
"to-arraybuffer": "^1.0.1",
|
||||||
"transform-loader": "^0.2.3",
|
"transform-loader": "^0.2.4",
|
||||||
"voice-activity-detection": "johni0702/voice-activity-detection#9f8bd90",
|
"voice-activity-detection": "johni0702/voice-activity-detection#9f8bd90",
|
||||||
"webpack": "^1.13.3",
|
"webpack": "^4.42.1",
|
||||||
"mumble-client-websocket": "^1.0.0",
|
"webpack-cli": "^3.3.11",
|
||||||
"mumble-client": "github:johni0702/mumble-client#ddf8424"
|
"worker-loader": "^2.0.0"
|
||||||
}
|
},
|
||||||
|
"optionalDependencies": {}
|
||||||
}
|
}
|
||||||
|
|
183
patches/mumble-client-codecs-browser+1.2.0.patch
Normal file
183
patches/mumble-client-codecs-browser+1.2.0.patch
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
diff --git a/node_modules/mumble-client-codecs-browser/lib/decode-worker.js b/node_modules/mumble-client-codecs-browser/lib/decode-worker.js
|
||||||
|
index 3925f29..be9af92 100644
|
||||||
|
--- a/node_modules/mumble-client-codecs-browser/lib/decode-worker.js
|
||||||
|
+++ b/node_modules/mumble-client-codecs-browser/lib/decode-worker.js
|
||||||
|
@@ -1,10 +1,6 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-Object.defineProperty(exports, "__esModule", {
|
||||||
|
- value: true
|
||||||
|
-});
|
||||||
|
|
||||||
|
-exports.default = function (self) {
|
||||||
|
var opusDecoder, celt7Decoder;
|
||||||
|
self.addEventListener('message', function (e) {
|
||||||
|
var data = e.data;
|
||||||
|
@@ -55,10 +51,12 @@ exports.default = function (self) {
|
||||||
|
}, [_decoded.buffer]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
-};
|
||||||
|
+
|
||||||
|
|
||||||
|
var _libopus = require('libopus.js');
|
||||||
|
|
||||||
|
var _libcelt = require('libcelt7.js');
|
||||||
|
|
||||||
|
var MUMBLE_SAMPLE_RATE = 48000;
|
||||||
|
+
|
||||||
|
+export default null
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/node_modules/mumble-client-codecs-browser/lib/decoder-stream.js b/node_modules/mumble-client-codecs-browser/lib/decoder-stream.js
|
||||||
|
index 6cfda8b..28a9549 100644
|
||||||
|
--- a/node_modules/mumble-client-codecs-browser/lib/decoder-stream.js
|
||||||
|
+++ b/node_modules/mumble-client-codecs-browser/lib/decoder-stream.js
|
||||||
|
@@ -1,9 +1,5 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-Object.defineProperty(exports, "__esModule", {
|
||||||
|
- value: true
|
||||||
|
-});
|
||||||
|
-
|
||||||
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||||
|
|
||||||
|
var _stream = require('stream');
|
||||||
|
@@ -12,17 +8,11 @@ var _reusePool = require('reuse-pool');
|
||||||
|
|
||||||
|
var _reusePool2 = _interopRequireDefault(_reusePool);
|
||||||
|
|
||||||
|
-var _webworkify = require('webworkify');
|
||||||
|
-
|
||||||
|
-var _webworkify2 = _interopRequireDefault(_webworkify);
|
||||||
|
-
|
||||||
|
var _toArraybuffer = require('to-arraybuffer');
|
||||||
|
|
||||||
|
var _toArraybuffer2 = _interopRequireDefault(_toArraybuffer);
|
||||||
|
|
||||||
|
-var _decodeWorker = require('./decode-worker');
|
||||||
|
-
|
||||||
|
-var _decodeWorker2 = _interopRequireDefault(_decodeWorker);
|
||||||
|
+import DecodeWorker from './decode-worker';
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
@@ -33,7 +23,7 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
|
||||||
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
||||||
|
|
||||||
|
var pool = (0, _reusePool2.default)(function () {
|
||||||
|
- return (0, _webworkify2.default)(_decodeWorker2.default);
|
||||||
|
+ return new DecodeWorker();
|
||||||
|
});
|
||||||
|
// Prepare first worker
|
||||||
|
pool.recycle(pool.get());
|
||||||
|
@@ -48,11 +38,6 @@ var DecoderStream = function (_Transform) {
|
||||||
|
|
||||||
|
_this._worker = pool.get();
|
||||||
|
_this._worker.onmessage = function (msg) {
|
||||||
|
- if (_this._worker.objectURL) {
|
||||||
|
- // The object URL can now be revoked as the worker has been loaded
|
||||||
|
- window.URL.revokeObjectURL(_this._worker.objectURL);
|
||||||
|
- _this._worker.objectURL = null;
|
||||||
|
- }
|
||||||
|
_this._onMessage(msg.data);
|
||||||
|
};
|
||||||
|
return _this;
|
||||||
|
@@ -112,4 +97,5 @@ var DecoderStream = function (_Transform) {
|
||||||
|
return DecoderStream;
|
||||||
|
}(_stream.Transform);
|
||||||
|
|
||||||
|
-exports.default = DecoderStream;
|
||||||
|
\ No newline at end of file
|
||||||
|
+//exports.default = DecoderStream;
|
||||||
|
+export default DecoderStream
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/node_modules/mumble-client-codecs-browser/lib/encode-worker.js b/node_modules/mumble-client-codecs-browser/lib/encode-worker.js
|
||||||
|
index f7187ab..c2ebaa3 100644
|
||||||
|
--- a/node_modules/mumble-client-codecs-browser/lib/encode-worker.js
|
||||||
|
+++ b/node_modules/mumble-client-codecs-browser/lib/encode-worker.js
|
||||||
|
@@ -1,10 +1,6 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-Object.defineProperty(exports, "__esModule", {
|
||||||
|
- value: true
|
||||||
|
-});
|
||||||
|
|
||||||
|
-exports.default = function (self) {
|
||||||
|
var opusEncoder, celt7Encoder;
|
||||||
|
var bitrate;
|
||||||
|
self.addEventListener('message', function (e) {
|
||||||
|
@@ -70,7 +66,7 @@ exports.default = function (self) {
|
||||||
|
}, [_buffer]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
-};
|
||||||
|
+
|
||||||
|
|
||||||
|
var _libopus = require('libopus.js');
|
||||||
|
|
||||||
|
@@ -83,3 +79,5 @@ var _toArraybuffer2 = _interopRequireDefault(_toArraybuffer);
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
var MUMBLE_SAMPLE_RATE = 48000;
|
||||||
|
+
|
||||||
|
+export default null
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/node_modules/mumble-client-codecs-browser/lib/encoder-stream.js b/node_modules/mumble-client-codecs-browser/lib/encoder-stream.js
|
||||||
|
index 021f131..eeb9189 100644
|
||||||
|
--- a/node_modules/mumble-client-codecs-browser/lib/encoder-stream.js
|
||||||
|
+++ b/node_modules/mumble-client-codecs-browser/lib/encoder-stream.js
|
||||||
|
@@ -1,9 +1,5 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-Object.defineProperty(exports, "__esModule", {
|
||||||
|
- value: true
|
||||||
|
-});
|
||||||
|
-
|
||||||
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||||
|
|
||||||
|
var _stream = require('stream');
|
||||||
|
@@ -12,13 +8,7 @@ var _reusePool = require('reuse-pool');
|
||||||
|
|
||||||
|
var _reusePool2 = _interopRequireDefault(_reusePool);
|
||||||
|
|
||||||
|
-var _webworkify = require('webworkify');
|
||||||
|
-
|
||||||
|
-var _webworkify2 = _interopRequireDefault(_webworkify);
|
||||||
|
-
|
||||||
|
-var _encodeWorker = require('./encode-worker');
|
||||||
|
-
|
||||||
|
-var _encodeWorker2 = _interopRequireDefault(_encodeWorker);
|
||||||
|
+import EncodeWorker from './encode-worker'
|
||||||
|
|
||||||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||||
|
|
||||||
|
@@ -29,7 +19,7 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
|
||||||
|
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
|
||||||
|
|
||||||
|
var pool = (0, _reusePool2.default)(function () {
|
||||||
|
- return (0, _webworkify2.default)(_encodeWorker2.default);
|
||||||
|
+ return new EncodeWorker();
|
||||||
|
});
|
||||||
|
// Prepare first worker
|
||||||
|
pool.recycle(pool.get());
|
||||||
|
@@ -46,11 +36,6 @@ var EncoderStream = function (_Transform) {
|
||||||
|
|
||||||
|
_this._worker = pool.get();
|
||||||
|
_this._worker.onmessage = function (msg) {
|
||||||
|
- if (_this._worker.objectURL) {
|
||||||
|
- // The object URL can now be revoked as the worker has been loaded
|
||||||
|
- window.URL.revokeObjectURL(_this._worker.objectURL);
|
||||||
|
- _this._worker.objectURL = null;
|
||||||
|
- }
|
||||||
|
_this._onMessage(msg.data);
|
||||||
|
};
|
||||||
|
return _this;
|
||||||
|
@@ -96,4 +81,5 @@ var EncoderStream = function (_Transform) {
|
||||||
|
return EncoderStream;
|
||||||
|
}(_stream.Transform);
|
||||||
|
|
||||||
|
-exports.default = EncoderStream;
|
||||||
|
\ No newline at end of file
|
||||||
|
+//exports.default = EncoderStream;
|
||||||
|
+export default EncoderStream
|
||||||
|
\ No newline at end of file
|
|
@ -110,7 +110,7 @@ html, body {
|
||||||
height: 19px;
|
height: 19px;
|
||||||
}
|
}
|
||||||
.branch {
|
.branch {
|
||||||
float: left;
|
position: absolute;
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
background-color: $channels-bg-color;
|
background-color: $channels-bg-color;
|
||||||
|
@ -129,7 +129,7 @@ html, body {
|
||||||
}
|
}
|
||||||
.channel-tree,
|
.channel-tree,
|
||||||
.user-tree {
|
.user-tree {
|
||||||
float: left;
|
position: absolute;
|
||||||
}
|
}
|
||||||
.channel-tree::before,
|
.channel-tree::before,
|
||||||
.user-tree::before {
|
.user-tree::before {
|
||||||
|
@ -432,10 +432,10 @@ form {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.join-dialog {
|
.join-dialog {
|
||||||
width: 100%;
|
width: 300px;
|
||||||
height: 100%;
|
height: 100px;
|
||||||
top: 0px;
|
top: 50%;
|
||||||
left: 0px;
|
left: 50%;
|
||||||
}
|
}
|
||||||
.join-dialog .dialog-submit {
|
.join-dialog .dialog-submit {
|
||||||
float: none;
|
float: none;
|
||||||
|
@ -444,7 +444,7 @@ form {
|
||||||
top: calc(50% - 10px);
|
top: calc(50% - 10px);
|
||||||
left: calc(50% - 100px);
|
left: calc(50% - 100px);
|
||||||
}
|
}
|
||||||
.connect-dialog input[type=text] {
|
.connect-dialog input[type=text], select {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border: 1px $dialog-input-border-color solid;
|
border: 1px $dialog-input-border-color solid;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
var theme = 'MetroMumbleLight'
|
var theme = '../themes/MetroMumbleLight'
|
||||||
|
var path = require('path');
|
||||||
var path = require('path')
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
mode: 'development',
|
||||||
entry: {
|
entry: {
|
||||||
index: [
|
index: [
|
||||||
'./app/index.js',
|
'./app/index.js',
|
||||||
|
@ -12,42 +12,47 @@ module.exports = {
|
||||||
theme: './app/theme.js',
|
theme: './app/theme.js',
|
||||||
matrix: './app/matrix.js'
|
matrix: './app/matrix.js'
|
||||||
},
|
},
|
||||||
|
devtool: "cheap-source-map",
|
||||||
output: {
|
output: {
|
||||||
filename: '[name].js',
|
path: path.join(__dirname, 'dist'),
|
||||||
path: './dist'
|
chunkFilename: '[chunkhash].js',
|
||||||
|
filename: '[name].js'
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
postLoaders: [
|
rules: [
|
||||||
{
|
|
||||||
include: /mumble-streams\/lib\/data.js/,
|
|
||||||
loader: 'transform-loader?brfs'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
loaders: [
|
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
loader: 'babel-loader',
|
use: {
|
||||||
query: {
|
loader: 'babel-loader',
|
||||||
presets: ['es2015'],
|
options: {
|
||||||
plugins: ['transform-runtime']
|
presets: ['@babel/preset-env'],
|
||||||
|
plugins: ['@babel/plugin-transform-runtime']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.html$/,
|
test: /\.html$/,
|
||||||
loaders: [
|
use: [
|
||||||
'file-loader?name=[name].[ext]',
|
{
|
||||||
'extract-loader',
|
loader: 'file-loader',
|
||||||
'html-loader?' + JSON.stringify({
|
options: { 'name': '[name].[ext]' }
|
||||||
attrs: ['img:src', 'link:href'],
|
},
|
||||||
interpolate: 'require',
|
{
|
||||||
root: theme
|
loader: "extract-loader"
|
||||||
})
|
},
|
||||||
|
{
|
||||||
|
loader: 'html-loader',
|
||||||
|
options: {
|
||||||
|
attrs: ['img:src', 'link:href'],
|
||||||
|
root: theme
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
loaders: [
|
use: [
|
||||||
'file-loader',
|
'file-loader',
|
||||||
'extract-loader',
|
'extract-loader',
|
||||||
'css-loader'
|
'css-loader'
|
||||||
|
@ -55,7 +60,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.scss$/,
|
test: /\.scss$/,
|
||||||
loaders: [
|
use: [
|
||||||
'file-loader?name=[hash].css',
|
'file-loader?name=[hash].css',
|
||||||
'extract-loader',
|
'extract-loader',
|
||||||
'css-loader',
|
'css-loader',
|
||||||
|
@ -63,45 +68,42 @@ module.exports = {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
type: 'javascript/auto',
|
||||||
test: /manifest\.json$|\.xml$/,
|
test: /manifest\.json$|\.xml$/,
|
||||||
loaders: [
|
use: [
|
||||||
'file-loader',
|
'file-loader',
|
||||||
'extract-loader',
|
'extract-loader',
|
||||||
'regexp-replace-loader?' + JSON.stringify({
|
{
|
||||||
match: {
|
loader: 'regexp-replace-loader',
|
||||||
pattern: "#require\\('([^']*)'\\)",
|
options: {
|
||||||
flags: 'g'
|
match: {
|
||||||
},
|
pattern: "#require\\('([^']*)'\\)",
|
||||||
replaceWith: '"+require("$1")+"'
|
flags: 'g'
|
||||||
}),
|
},
|
||||||
|
replaceWith: '"+require("$1")+"'
|
||||||
|
}
|
||||||
|
},
|
||||||
'raw-loader'
|
'raw-loader'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.json$/,
|
test: /\.(svg|png|ico)$/,
|
||||||
exclude: /manifest\.json$/,
|
use: [
|
||||||
loader: 'json-loader'
|
'file-loader'
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(svg|png|ico)$/,
|
test: /worker\.js$/,
|
||||||
loader: 'file-loader'
|
use: { loader: 'worker-loader' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enforce: 'post',
|
||||||
|
test: /mumble-streams\/lib\/data.js/,
|
||||||
|
use: [
|
||||||
|
'transform-loader?brfs'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
resolve: {
|
target: 'web'
|
||||||
alias: {
|
|
||||||
webworkify: 'webworkify-webpack'
|
|
||||||
},
|
|
||||||
root: [
|
|
||||||
path.resolve('./themes/')
|
|
||||||
]
|
|
||||||
},
|
|
||||||
includes: {
|
|
||||||
pattern: function (filepath) {
|
|
||||||
return {
|
|
||||||
re: /#require\((.+)\)/,
|
|
||||||
index: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue