Display user avatars instead of talking indicators when possible

This commit is contained in:
Jonas Herzig 2018-09-24 15:36:37 +02:00
parent 2b7f7c1625
commit d7b03d2192
4 changed files with 89 additions and 3 deletions

View file

@ -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'"

View file

@ -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)

View file

@ -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"
} }
} }

View file

@ -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);
} }