Merge pull request #8341 from nextcloud/feature/6766/add-useful-information-to-the-topbar

⏺️ Recording part 1 - Add useful information to the top-bar
This commit is contained in:
Joas Schilling 2022-11-22 15:35:44 +01:00 коммит произвёл GitHub
Родитель 6761f84577 c18297d490
Коммит 63f60ea1eb
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 328 добавлений и 61 удалений

Просмотреть файл

@ -552,6 +552,11 @@ export default {
padding: 0 !important;
}
::v-deep .app-navigation-toggle {
top: 8px !important;
right: -6px !important;
}
::v-deep .app-navigation__list {
padding: 0 !important;
}

Просмотреть файл

@ -95,7 +95,7 @@
</template>
<script>
import { emit } from '@nextcloud/event-bus'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
import NcAppSidebar from '@nextcloud/vue/dist/Components/NcAppSidebar.js'
import NcAppSidebarTab from '@nextcloud/vue/dist/Components/NcAppSidebarTab.js'
import SharedItemsTab from './SharedItems/SharedItemsTab.vue'
@ -274,6 +274,14 @@ export default {
},
},
mounted() {
subscribe('spreed:select-active-sidebar-tab', this.handleUpdateActive)
},
beforeDestroy() {
unsubscribe('spreed:select-active-sidebar-tab', this.handleUpdateActive)
},
methods: {
handleClose() {
this.dismissEditing()

Просмотреть файл

@ -0,0 +1,205 @@
<!--
- @copyright Copyright (c) 2022 Marco Ambrosini <marcoambrosini@icloud.com> -
- @author Marco Ambrosini <marcoambrosini@icloud.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<NcPopover class="top-bar__button call-time"
close-after-click="true"
:menu-title="callTime"
:shown.sync="showPopover"
:class="{ 'call-time--wide': isWide }"
:triggers="[]"
:container="container">
<template #trigger>
<NcButton :disabled="!isRecording || !isModerator"
:wide="true"
:class="{ 'call-time__not-recording': !isRecording }"
type="tertiary"
@click="showPopover = true">
<template v-if="isRecording" #icon>
<RecordCircle :size="20"
fill-color="#e9322d" />
</template>
{{ formattedTime }}
</ncbutton>
</template>
<NcButton type="tertiary-no-background"
:wide="true"
@click="stopRecording">
<template #icon>
<StopIcon :size="20" />
</template>
{{ t('spreed', 'Stop recording') }}
</NcButton>
</NcPopover>
</template>
<script>
import RecordCircle from 'vue-material-design-icons/RecordCircle.vue'
import StopIcon from 'vue-material-design-icons/Stop.vue'
import NcPopover from '@nextcloud/vue/dist/Components/NcPopover.js'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import isInLobby from '../../mixins/isInLobby.js'
export default {
name: 'CallTime',
components: {
RecordCircle,
StopIcon,
NcPopover,
NcButton,
},
mixins: [isInLobby],
props: {
/**
* Unix timestamp representing the start of the call
*/
start: {
type: Number,
required: true,
},
isRecording: {
type: Boolean,
required: true,
},
canModerate: {
type: Boolean,
required: true,
},
},
data() {
return {
callTime: undefined,
showPopover: false,
timer: null,
}
},
computed: {
container() {
return this.$store.getters.getMainContainerSelector()
},
/**
* Create date object based on the unix time received from the API
*
* @return {Date} The date object
*/
callStart() {
return new Date(this.start * 1000)
},
/**
* Calculates the stopwatch string given the callTime (ms)
*
* @return {string} The formatted time
*/
formattedTime() {
if (!this.callTime) {
return '-- : --'
}
let seconds = Math.floor((this.callTime / 1000) % 60)
if (seconds < 10) {
seconds = '0' + seconds
}
let minutes = Math.floor((this.callTime / (1000 * 60)) % 60)
if (minutes < 10) {
minutes = '0' + minutes
}
const hours = Math.floor((this.callTime / (1000 * 60 * 60)) % 24)
if (hours === 0) {
return minutes + ' : ' + seconds
}
return hours + ' : ' + minutes + ' : ' + seconds
},
isWide() {
return this.formattedTime.length > 7
},
token() {
return this.$store.getters.getToken()
},
conversation() {
return this.$store.getters.conversation(this.token) || this.$store.getters.dummyConversation
},
},
mounted() {
// Start the timer when mounted
this.timer = setInterval(this.computeElapsedTime, 1000)
},
beforeDestroy() {
clearInterval(this.timer)
},
methods: {
stopRecording() {
this.$emit('stop-recording')
this.showPopover = false
},
computeElapsedTime() {
if (this.start === 0) {
return
}
this.callTime = new Date() - this.callStart
},
},
}
</script>
<style lang="scss" scoped>
.call-time {
display: flex;
justify-content: center;
align-items: center;
height: var(--default-clickable-area);
font-weight: bold;
width: 116px;
&__not-recording {
padding-left: var(--default-clickable-area) !important
}
&--wide {
width: 148px;
}
}
::v-deep .button-vue {
justify-content: left !important;
color: #fff !important;
&:disabled {
opacity: 1 !important;
}
}
</style>

Просмотреть файл

@ -21,16 +21,14 @@
<template>
<div class="top-bar" :class="{ 'in-call': isInCall }">
<ConversationIcon v-if="!isInCall"
:key="conversation.token"
<ConversationIcon :key="conversation.token"
class="conversation-icon"
:offline="isPeerOffline"
:item="conversation"
:hide-favorite="false"
:hide-call="false" />
<!-- conversation header -->
<a v-if="!isInCall"
role="button"
<a role="button"
class="conversation-header"
@click="openConversationSettings">
<div class="conversation-header__text"
@ -42,18 +40,27 @@
class="description">
{{ statusMessage }}
</p>
<p v-else-if="conversation.description"
v-tooltip.bottom="{
content: renderedDescription,
delay: { show: 500, hide: 500 },
autoHide: false,
html: true,
}"
class="description">
{{ conversation.description }}
</p>
<template v-if="!isInCall && conversation.description">
<p v-tooltip.bottom="{
content: renderedDescription,
delay: { show: 500, hide: 500 },
autoHide: false,
html: true,
}"
class="description">
{{ conversation.description }}
</p>
</template>
</div>
</a>
<!-- Call time -->
<CallTime v-if="isInCall"
:start="conversation.callStartTime"
:is-recording="isRecording"
@stop-recording="isRecording = false" />
<!-- Local media controls -->
<LocalMediaControls v-if="isInCall"
class="local-media-controls"
:token="token"
@ -68,36 +75,54 @@
:is-sidebar="isSidebar"
:model="localMediaModel" />
<div class="top-bar__buttons">
<CallButton class="top-bar__button" />
<CallButton class="top-bar__button" />
<template v-if="showOpenSidebarButton">
<!-- sidebar toggle -->
<NcActions v-if="showOpenSidebarButton"
<NcButton v-if="!isInCall"
class="top-bar__button"
close-after-click="true"
:container="container">
<NcActionButton v-if="isInCall"
key="openSideBarButtonMessageText"
@click="openSidebar">
<template #icon>
<MessageText :size="20"
fill-color="#ffffff" />
</template>
</NcActionButton>
<NcActionButton v-else
key="openSideBarButtonMenuPeople"
@click="openSidebar">
<template #icon>
<MenuPeople :size="20" />
</template>
</NcActionButton>
</NcActions>
</div>
<NcCounterBubble v-if="!isSidebar && showOpenSidebarButton && isInCall && unreadMessagesCounter > 0"
class="unread-messages-counter"
:highlighted="hasUnreadMentions">
{{ unreadMessagesCounter }}
</NcCounterBubble>
type="tertiary"
@click="openSidebar">
<template #icon>
<MenuIcon :size="20" />
</template>
</NcButton>
<!-- chat button -->
<div v-if="isInCall"
class="chat-button">
<NcActions class="top-bar__button"
close-after-click="true"
:container="container">
<NcActionButton key="openSideBarButtonMessageText"
@click="openSidebar('chat')">
<template #icon>
<MessageText :size="20"
fill-color="#ffffff" />
</template>
</NcActionButton>
</NcActions>
<NcCounterBubble v-if="!isSidebar && isInCall && unreadMessagesCounter > 0"
class="chat-button__unread-messages-counter"
:highlighted="hasUnreadMentions">
{{ unreadMessagesCounter }}
</NcCounterBubble>
</div>
<!-- participants button -->
<NcButton v-if="isInCall && !isOneToOneConversation"
class="top-bar__button"
close-after-click="true"
type="tertiary"
@click="openSidebar('participants')">
<template #icon>
<AccountMultiple :size="20"
fill-color="#ffffff" />
</template>
{{ participantsInCall }}
</NcButton>
</template>
</div>
</template>
@ -108,7 +133,7 @@ import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js'
import CallButton from './CallButton.vue'
import BrowserStorage from '../../services/BrowserStorage.js'
import MenuPeople from '../missingMaterialDesignIcons/MenuPeople.vue'
import AccountMultiple from 'vue-material-design-icons/AccountMultiple.vue'
import MessageText from 'vue-material-design-icons/MessageText.vue'
import { CONVERSATION } from '../../constants.js'
import { generateUrl } from '@nextcloud/router'
@ -121,6 +146,9 @@ import userStatus from '../../mixins/userStatus.js'
import LocalMediaControls from '../CallView/shared/LocalMediaControls.vue'
import getParticipants from '../../mixins/getParticipants.js'
import TopBarMenu from './TopBarMenu.vue'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import CallTime from './CallTime.vue'
import MenuIcon from 'vue-material-design-icons/Menu.vue'
export default {
name: 'TopBar',
@ -134,11 +162,14 @@ export default {
NcActions,
NcCounterBubble,
CallButton,
MenuPeople,
AccountMultiple,
MessageText,
ConversationIcon,
LocalMediaControls,
TopBarMenu,
NcButton,
CallTime,
MenuIcon,
},
mixins: [
@ -167,7 +198,8 @@ export default {
unreadNotificationHandle: null,
localCallParticipantModel,
localMediaModel,
// TODO: real value
isRecording: true,
}
},
@ -248,6 +280,10 @@ export default {
return !peer.sessionIds.length
} else return false
},
participantsInCall() {
return this.$store.getters.participantsInCall(this.token) ? this.$store.getters.participantsInCall(this.token) : ''
},
},
watch: {
@ -317,7 +353,10 @@ export default {
}
},
openSidebar() {
openSidebar(activeTab) {
if (typeof activeTab === 'string') {
emit('spreed:select-active-sidebar-tab', activeTab)
}
this.$store.dispatch('showSidebar')
BrowserStorage.setItem('sidebarOpen', 'true')
},
@ -325,6 +364,11 @@ export default {
openConversationSettings() {
emit('show-conversation-settings', { token: this.token })
},
// TODO: implement real method
stopRecording() {
console.debug('stop recordiiing')
},
},
}
</script>
@ -354,12 +398,10 @@ export default {
left:0;
background-color: transparent;
display: flex;
flex-wrap: wrap-reverse;
}
&__buttons {
display: flex;
margin-left: 8px;
flex-wrap: wrap;
& * {
color: #fff;
}
}
&__button {
@ -377,11 +419,14 @@ export default {
}
}
.unread-messages-counter {
position: absolute;
top: 40px;
right: 4px;
pointer-events: none;
.chat-button {
position: relative;
&__unread-messages-counter {
position: absolute;
top: 24px;
right: 2px;
pointer-events: none;
}
}
}
@ -395,7 +440,8 @@ export default {
overflow-x: hidden;
overflow-y: clip;
white-space: nowrap;
width: 100%;
width: 0;
flex-grow: 1;
cursor: pointer;
&__text {
display: flex;
@ -422,8 +468,4 @@ export default {
color: var(--color-text-lighter);
}
}
.local-media-controls {
padding-left: $clickable-area;
}
</style>

Просмотреть файл

@ -155,6 +155,13 @@ const getters = {
return {}
},
participantsInCall: (state) => (token) => {
if (state.attendees[token]) {
return Object.values(state.attendees[token]).filter(attendee => attendee.inCall !== PARTICIPANT.CALL_FLAG.DISCONNECTED).length
}
return 0
},
}
const mutations = {