Skip to content

Commit

Permalink
show speaking participants and elapsed time in the sidebar tab
Browse files Browse the repository at this point in the history
Signed-off-by: Maksim Sukharev <[email protected]>
  • Loading branch information
Antreesy committed Aug 8, 2023
1 parent 650d0dd commit 6cd5a21
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 3 deletions.
11 changes: 11 additions & 0 deletions src/components/AvatarWrapper/AvatarWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
'avatar-wrapper--offline': offline,
'avatar-wrapper--small': small,
'avatar-wrapper--condensed': condensed,
'avatar-wrapper--highlighted': highlighted,
}"
:style="{'--condensed-overlap': condensedOverlap}">
<div v-if="iconClass"
Expand Down Expand Up @@ -89,6 +90,10 @@ export default {
type: Boolean,
default: false,
},
highlighted: {
type: Boolean,
default: false,
},
disableTooltip: {
type: Boolean,
default: false,
Expand Down Expand Up @@ -160,11 +165,13 @@ export default {
.avatar-wrapper {
height: 44px;
width: 44px;
border-radius: 44px;
@include avatar-mixin(44px);
&--small {
height: 22px;
width: 22px;
border-radius: 22px;
@include avatar-mixin(22px);
}
Expand All @@ -188,6 +195,10 @@ export default {
background: rgba(var(--color-main-background-rgb), .4) !important;
}
}
&--highlighted {
outline: 2px solid var(--color-primary-element);
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
disable-tooltip
:show-user-status="showUserStatus && !isSearched"
:preloaded-user-status="preloadedUserStatus"
:highlighted="isParticipantSpeaking"
:offline="isOffline" />

<!-- Participant's data -->
Expand Down Expand Up @@ -67,6 +68,7 @@
<div v-else-if="statusMessage"
ref="statusMessage"
class="participant-row__status"
:class="{'participant-row__status--highlighted': isParticipantSpeaking}"
@mouseover="updateStatusNeedsTooltip()">
<span v-tooltip.auto="statusMessageTooltip">{{ statusMessage }}</span>
</div>
Expand Down Expand Up @@ -230,11 +232,11 @@ import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js'
import AvatarWrapper from '../../../../AvatarWrapper/AvatarWrapper.vue'
import ParticipantPermissionsEditor from './ParticipantPermissionsEditor/ParticipantPermissionsEditor.vue'
import { useIsInCall } from '../../../../../composables/useIsInCall.js'
import { CONVERSATION, PARTICIPANT, ATTENDEE } from '../../../../../constants.js'
import readableNumber from '../../../../../mixins/readableNumber.js'
import UserStatus from '../../../../../mixins/userStatus.js'
// Material design icons
import { formattedTime } from '../../../../../utils/formattedTime.js'
export default {
name: 'Participant',
Expand Down Expand Up @@ -298,11 +300,18 @@ export default {
emits: ['click-participant'],
setup() {
const isInCall = useIsInCall()
return { isInCall }
},
data() {
return {
isUserNameTooltipVisible: false,
isStatusTooltipVisible: false,
permissionsEditor: false,
speakingInterval: null,
timeSpeaking: null,
}
},
Expand Down Expand Up @@ -341,7 +350,13 @@ export default {
},
statusMessage() {
return this.getStatusMessage(this.participant)
if (this.isInCall && this.participant.inCall && this.timeSpeaking) {
return this.isParticipantSpeaking
? '💬 ' + t('spreed', '{time} talking …', { time: formattedTime(this.timeSpeaking, true) })
: '💬 ' + t('spreed', '{time} talking time', { time: formattedTime(this.timeSpeaking, true) })
} else {
return this.getStatusMessage(this.participant)
}
},
statusMessageTooltip() {
Expand Down Expand Up @@ -483,6 +498,14 @@ export default {
return this.participant.sessionIds || []
},
participantSpeakingInformation() {
return this.$store.getters.getParticipantSpeakingInformation(this.token, this.sessionIds)
},
isParticipantSpeaking() {
return this.participantSpeakingInformation?.speaking
},
lastPing() {
return this.participant.lastPing
},
Expand Down Expand Up @@ -619,8 +642,25 @@ export default {
return ''
},
},
watch: {
isParticipantSpeaking(speaking) {
if (speaking) {
if (!this.speakingInterval) {
this.speakingInterval = setInterval(this.computeElapsedTime, 1000)
}
} else {
if (speaking === undefined) {
this.timeSpeaking = 0
}
clearInterval(this.speakingInterval)
this.speakingInterval = null
}
},
},
methods: {
formattedTime,
updateUserNameNeedsTooltip() {
// check if ellipsized
const e = this.$refs.userName
Expand Down Expand Up @@ -724,6 +764,20 @@ export default {
showError(t('spreed', 'Could not modify permissions for {displayName}', { displayName: this.computedName }))
}
},
computeElapsedTime() {
if (!this.participantSpeakingInformation) {
return null
}
const { speaking, lastTimestamp, totalCountedTime } = this.participantSpeakingInformation
if (!speaking) {
this.timeSpeaking = totalCountedTime
} else {
this.timeSpeaking = Date.now() - lastTimestamp + totalCountedTime
}
},
},
}
</script>
Expand Down Expand Up @@ -789,6 +843,9 @@ export default {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&--highlighted {
font-weight: bold;
}
}
&__icon {
width: 44px;
Expand Down
18 changes: 18 additions & 0 deletions src/store/participantsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,24 @@ const getters = {
})
},

/**
* Gets the speaking information for the participant.
*
* @param {object} state - the state object.
* param {string} token - the conversation token.
* param {Array<string>} sessionIds - session identifiers for the participant.
* @return {object|undefined}
*/
getParticipantSpeakingInformation: (state) => (token, sessionIds) => {
if (!state.speaking[token]) {
return undefined
}

// look for existing sessionId in the store
const sessionId = sessionIds.find(sessionId => state.speaking[token][sessionId])
return state.speaking[token][sessionId]
},

/**
* Replaces the legacy getParticipant getter. Returns a callback function in which you can
* pass in the token and attendeeId as arguments to get the participant object.
Expand Down

0 comments on commit 6cd5a21

Please sign in to comment.