Display user avatars instead of talking indicators when possible
This commit is contained in:
parent
2b7f7c1625
commit
d7b03d2192
|
@ -160,6 +160,17 @@
|
||||||
<input type="button" data-bind="value: pttKeyDisplay, click: recordPttKey">
|
<input type="button" data-bind="value: pttKeyDisplay, click: recordPttKey">
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Show Avatars</td>
|
||||||
|
<td>
|
||||||
|
<select data-bind='value: showAvatars'>
|
||||||
|
<option value="always">Always</option>
|
||||||
|
<option value="own_channel">Same Channel</option>
|
||||||
|
<option value="linked_channel">Linked Channels</option>
|
||||||
|
<option value="minimal_only">Minimal View</option>
|
||||||
|
<option value="never">Never</option>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<input class="dialog-close" type="button" data-bind="click: $root.closeSettings" value="Cancel">
|
<input class="dialog-close" type="button" data-bind="click: $root.closeSettings" value="Cancel">
|
||||||
|
@ -304,6 +315,15 @@
|
||||||
<div data-bind="if: comment">
|
<div data-bind="if: comment">
|
||||||
<div class="user-comment tooltip" data-bind="html: comment"></div>
|
<div class="user-comment tooltip" data-bind="html: comment"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- ko if: show_avatar() -->
|
||||||
|
<img class="user-avatar" alt="avatar"
|
||||||
|
data-bind="attr: { src: texture },
|
||||||
|
css: { 'user-avatar-talk-off': talking() == 'off',
|
||||||
|
'user-avatar-talk-on': talking() == 'on',
|
||||||
|
'user-avatar-talk-whisper': talking() == 'whisper',
|
||||||
|
'user-avatar-talk-shout': talking() == 'shout' }">
|
||||||
|
<!-- /ko -->
|
||||||
|
<!-- ko ifnot: show_avatar() -->
|
||||||
<img class="user-talk user-talk-off" data-bind="visible: talking() == 'off'"
|
<img class="user-talk user-talk-off" data-bind="visible: talking() == 'off'"
|
||||||
alt="talk off" src="/svg/talking_off.svg">
|
alt="talk off" src="/svg/talking_off.svg">
|
||||||
<img class="user-talk user-talk-on" data-bind="visible: talking() == 'on'"
|
<img class="user-talk user-talk-on" data-bind="visible: talking() == 'on'"
|
||||||
|
|
45
app/index.js
45
app/index.js
|
@ -78,6 +78,7 @@ class SettingsDialog {
|
||||||
this.vadLevel = ko.observable(settings.vadLevel)
|
this.vadLevel = ko.observable(settings.vadLevel)
|
||||||
this.testVadLevel = ko.observable(0)
|
this.testVadLevel = ko.observable(0)
|
||||||
this.testVadActive = ko.observable(false)
|
this.testVadActive = ko.observable(false)
|
||||||
|
this.showAvatars = ko.observable(settings.showAvatars())
|
||||||
|
|
||||||
this._setupTestVad()
|
this._setupTestVad()
|
||||||
this.vadLevel.subscribe(() => this._setupTestVad())
|
this.vadLevel.subscribe(() => this._setupTestVad())
|
||||||
|
@ -98,6 +99,7 @@ class SettingsDialog {
|
||||||
settings.voiceMode = this.voiceMode()
|
settings.voiceMode = this.voiceMode()
|
||||||
settings.pttKey = this.pttKey()
|
settings.pttKey = this.pttKey()
|
||||||
settings.vadLevel = this.vadLevel()
|
settings.vadLevel = this.vadLevel()
|
||||||
|
settings.showAvatars(this.showAvatars())
|
||||||
}
|
}
|
||||||
|
|
||||||
end () {
|
end () {
|
||||||
|
@ -133,6 +135,7 @@ class Settings {
|
||||||
this.pttKey = load('pttKey') || 'ctrl + shift'
|
this.pttKey = load('pttKey') || 'ctrl + shift'
|
||||||
this.vadLevel = load('vadLevel') || 0.3
|
this.vadLevel = load('vadLevel') || 0.3
|
||||||
this.toolbarVertical = load('toolbarVertical') || false
|
this.toolbarVertical = load('toolbarVertical') || false
|
||||||
|
this.showAvatars = ko.observable(load('showAvatars') || 'always')
|
||||||
}
|
}
|
||||||
|
|
||||||
save () {
|
save () {
|
||||||
|
@ -141,6 +144,7 @@ class Settings {
|
||||||
save('pttKey', this.pttKey)
|
save('pttKey', this.pttKey)
|
||||||
save('vadLevel', this.vadLevel)
|
save('vadLevel', this.vadLevel)
|
||||||
save('toolbarVertical', this.toolbarVertical)
|
save('toolbarVertical', this.toolbarVertical)
|
||||||
|
save('showAvatars', this.showAvatars())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,6 +299,8 @@ class GlobalBindings {
|
||||||
suppress: 'suppress',
|
suppress: 'suppress',
|
||||||
selfMute: 'selfMute',
|
selfMute: 'selfMute',
|
||||||
selfDeaf: 'selfDeaf',
|
selfDeaf: 'selfDeaf',
|
||||||
|
texture: 'rawTexture',
|
||||||
|
textureHash: 'textureHash',
|
||||||
comment: 'comment'
|
comment: 'comment'
|
||||||
}
|
}
|
||||||
var ui = user.__ui = {
|
var ui = user.__ui = {
|
||||||
|
@ -302,6 +308,40 @@ class GlobalBindings {
|
||||||
talking: ko.observable('off'),
|
talking: ko.observable('off'),
|
||||||
channel: ko.observable()
|
channel: ko.observable()
|
||||||
}
|
}
|
||||||
|
ui.texture = ko.pureComputed(() => {
|
||||||
|
let raw = ui.rawTexture()
|
||||||
|
if (!raw || raw.offset >= raw.limit) return null
|
||||||
|
return 'data:image/*;base64,' + raw.toBase64()
|
||||||
|
})
|
||||||
|
ui.show_avatar = () => {
|
||||||
|
let setting = this.settings.showAvatars()
|
||||||
|
switch (setting) {
|
||||||
|
case 'always':
|
||||||
|
break
|
||||||
|
case 'own_channel':
|
||||||
|
if (this.thisUser().channel() !== ui.channel()) return false
|
||||||
|
break
|
||||||
|
case 'linked_channel':
|
||||||
|
if (!ui.channel().linked()) return false
|
||||||
|
break
|
||||||
|
case 'minimal_only':
|
||||||
|
if (!this.minimalView()) return false
|
||||||
|
if (this.thisUser().channel() !== ui.channel()) return false
|
||||||
|
break
|
||||||
|
case 'never':
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
if (!ui.texture()) {
|
||||||
|
if (ui.textureHash()) {
|
||||||
|
// The user has an avatar set but it's of sufficient size to not be
|
||||||
|
// included by default, so we need to fetch it explicitly now.
|
||||||
|
// mumble-client should make sure we only send one request per hash
|
||||||
|
user.requestTexture()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
Object.entries(simpleProperties).forEach(key => {
|
Object.entries(simpleProperties).forEach(key => {
|
||||||
ui[key[1]] = ko.observable(user[key[0]])
|
ui[key[1]] = ko.observable(user[key[0]])
|
||||||
})
|
})
|
||||||
|
@ -327,6 +367,11 @@ class GlobalBindings {
|
||||||
ui.channel().users.sort(compareUsers)
|
ui.channel().users.sort(compareUsers)
|
||||||
this._updateLinks()
|
this._updateLinks()
|
||||||
}
|
}
|
||||||
|
if (properties.textureHash !== undefined) {
|
||||||
|
// Invalidate avatar texture when its hash has changed
|
||||||
|
// If the avatar is still visible, this will trigger a fetch of the new one.
|
||||||
|
ui.rawTexture(null)
|
||||||
|
}
|
||||||
}).on('remove', () => {
|
}).on('remove', () => {
|
||||||
if (ui.channel()) {
|
if (ui.channel()) {
|
||||||
ui.channel().users.remove(ui)
|
ui.channel().users.remove(ui)
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
"libsamplerate.js": "^1.0.0",
|
"libsamplerate.js": "^1.0.0",
|
||||||
"mumble-client-codecs-browser": "^1.1.1",
|
"mumble-client-codecs-browser": "^1.1.1",
|
||||||
"mumble-client-websocket": "^1.0.0",
|
"mumble-client-websocket": "^1.0.0",
|
||||||
"mumble-client": "^1.1.1",
|
"mumble-client": "^1.2.0",
|
||||||
"web-audio-buffer-queue": "^1.0.0"
|
"web-audio-buffer-queue": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,9 @@ $chat-channel-color: orange !default
|
||||||
$chat-user-color: green !default
|
$chat-user-color: green !default
|
||||||
$chat-input-color: $font-color !default
|
$chat-input-color: $font-color !default
|
||||||
$mic-volume-border-color: $black !default
|
$mic-volume-border-color: $black !default
|
||||||
|
$talk-outline-color: green !default
|
||||||
|
$whisper-outline-color: purple !default
|
||||||
|
$shout-outline-color: cyan !default
|
||||||
|
|
||||||
$toolbar-hover-bg-color: $lightgray !default
|
$toolbar-hover-bg-color: $lightgray !default
|
||||||
$toolbar-hover-border-color: $gray !default
|
$toolbar-hover-border-color: $gray !default
|
||||||
|
@ -144,9 +147,27 @@ html, body {
|
||||||
.user {
|
.user {
|
||||||
margin-left: 9px;
|
margin-left: 9px;
|
||||||
}
|
}
|
||||||
.user-talk {
|
.user-avatar, .user-talk {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
@mixin drop-shadow-4x($size, $blur, $color) {
|
||||||
|
filter: drop-shadow(#{+$size} #{+$size} $blur $color)
|
||||||
|
drop-shadow(#{+$size} #{-$size} $blur $color)
|
||||||
|
drop-shadow(#{-$size} #{+$size} $blur $color)
|
||||||
|
drop-shadow(#{-$size} #{-$size} $blur $color);
|
||||||
|
}
|
||||||
|
@mixin user-avatar-drop-shadow($color) {
|
||||||
|
@include drop-shadow-4x(1px, 1px, $color);
|
||||||
|
}
|
||||||
|
.user-avatar-talk-on {
|
||||||
|
@include user-avatar-drop-shadow($talk-outline-color);
|
||||||
|
}
|
||||||
|
.user-avatar-talk-whisper {
|
||||||
|
@include user-avatar-drop-shadow($whisper-outline-color);
|
||||||
|
}
|
||||||
|
.user-avatar-talk-shout {
|
||||||
|
@include user-avatar-drop-shadow($shout-outline-color);
|
||||||
|
}
|
||||||
.user-status, .channel-status {
|
.user-status, .channel-status {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
@ -339,7 +360,7 @@ form {
|
||||||
}
|
}
|
||||||
.settings-dialog {
|
.settings-dialog {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 156px;
|
height: 200px;
|
||||||
top: calc(50% - 100px);
|
top: calc(50% - 100px);
|
||||||
left: calc(50% - 150px);
|
left: calc(50% - 150px);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue