summaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/CallView/CallView.vue3
-rw-r--r--src/components/CallView/Grid/Grid.vue2
-rw-r--r--src/components/CallView/shared/LocalMediaControls.vue60
-rw-r--r--src/components/CallView/shared/LocalVideo.vue5
-rw-r--r--src/components/Description/Description.vue6
-rw-r--r--src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue4
-rw-r--r--src/components/MessagesList/MessagesGroup/Message/Message.vue26
-rw-r--r--src/components/MessagesList/MessagesGroup/Message/MessagePart/AudioPlayer.vue82
-rw-r--r--src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue10
-rw-r--r--src/components/NewMessageForm/AdvancedInput/AdvancedInput.vue7
-rw-r--r--src/components/NewMessageForm/AudioRecorder/AudioRecorder.vue351
-rw-r--r--src/components/NewMessageForm/NewMessageForm.vue69
-rw-r--r--src/components/TopBar/CallButton.vue7
-rw-r--r--src/components/UploadEditor.vue14
14 files changed, 614 insertions, 32 deletions
diff --git a/src/components/CallView/CallView.vue b/src/components/CallView/CallView.vue
index c289a2129..5f470f3db 100644
--- a/src/components/CallView/CallView.vue
+++ b/src/components/CallView/CallView.vue
@@ -30,6 +30,7 @@
<LocalMediaControls
class="local-media-controls"
:class="{ 'local-media-controls--sidebar': isSidebar }"
+ :token="token"
:model="localMediaModel"
:show-actions="!isSidebar"
:local-call-participant-model="localCallParticipantModel"
@@ -90,6 +91,7 @@
:is-stripe="false"
:show-controls="false"
:is-big="true"
+ :token="token"
:local-media-model="localMediaModel"
:video-container-aspect-ratio="videoContainerAspectRatio"
:local-call-participant-model="localCallParticipantModel"
@@ -143,6 +145,7 @@
:show-controls="false"
:fit-video="true"
:is-stripe="true"
+ :token="token"
:local-media-model="localMediaModel"
:video-container-aspect-ratio="videoContainerAspectRatio"
:local-call-participant-model="localCallParticipantModel"
diff --git a/src/components/CallView/Grid/Grid.vue b/src/components/CallView/Grid/Grid.vue
index d4191ff3e..e7b0f8d72 100644
--- a/src/components/CallView/Grid/Grid.vue
+++ b/src/components/CallView/Grid/Grid.vue
@@ -112,6 +112,7 @@
class="video"
:is-grid="true"
:fit-video="isStripe"
+ :token="token"
:local-media-model="localMediaModel"
:video-container-aspect-ratio="videoContainerAspectRatio"
:local-call-participant-model="localCallParticipantModel"
@@ -135,6 +136,7 @@
:fit-video="true"
:is-stripe="true"
:show-controls="false"
+ :token="token"
:local-media-model="localMediaModel"
:video-container-aspect-ratio="videoContainerAspectRatio"
:local-call-participant-model="localCallParticipantModel"
diff --git a/src/components/CallView/shared/LocalMediaControls.vue b/src/components/CallView/shared/LocalMediaControls.vue
index a4c6a4382..a4fbb09b2 100644
--- a/src/components/CallView/shared/LocalMediaControls.vue
+++ b/src/components/CallView/shared/LocalMediaControls.vue
@@ -219,6 +219,7 @@ import Video from 'vue-material-design-icons/Video'
import VideoOff from 'vue-material-design-icons/VideoOff'
import Popover from '@nextcloud/vue/dist/Components/Popover'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
+import { PARTICIPANT } from '../../../constants'
import SpeakingWhileMutedWarner from '../../../utils/webrtc/SpeakingWhileMutedWarner'
import NetworkStrength2Alert from 'vue-material-design-icons/NetworkStrength2Alert'
import { Actions, ActionSeparator, ActionButton } from '@nextcloud/vue'
@@ -249,6 +250,10 @@ export default {
},
props: {
+ token: {
+ type: String,
+ required: true,
+ },
model: {
type: Object,
required: true,
@@ -289,10 +294,26 @@ export default {
return t('spreed', 'Lower hand (R)')
},
+ conversation() {
+ return this.$store.getters.conversation(this.token) || this.$store.getters.dummyConversation
+ },
+
+ isAudioAllowed() {
+ return this.conversation.publishingPermissions === PARTICIPANT.PUBLISHING_PERMISSIONS.ALL
+ },
+
+ isVideoAllowed() {
+ return this.conversation.publishingPermissions === PARTICIPANT.PUBLISHING_PERMISSIONS.ALL
+ },
+
+ isScreensharingAllowed() {
+ return this.conversation.publishingPermissions === PARTICIPANT.PUBLISHING_PERMISSIONS.ALL
+ },
+
audioButtonClass() {
return {
- 'audio-disabled': this.model.attributes.audioAvailable && !this.model.attributes.audioEnabled,
- 'no-audio-available': !this.model.attributes.audioAvailable,
+ 'audio-disabled': this.isAudioAllowed && this.model.attributes.audioAvailable && !this.model.attributes.audioEnabled,
+ 'no-audio-available': !this.isAudioAllowed || !this.model.attributes.audioAvailable,
}
},
@@ -301,6 +322,10 @@ export default {
},
audioButtonTooltip() {
+ if (!this.isAudioAllowed) {
+ return t('spreed', 'You are not allowed to enable audio')
+ }
+
if (!this.model.attributes.audioAvailable) {
return {
content: t('spreed', 'No audio'),
@@ -348,8 +373,8 @@ export default {
videoButtonClass() {
return {
- 'video-disabled': this.model.attributes.videoAvailable && !this.model.attributes.videoEnabled,
- 'no-video-available': !this.model.attributes.videoAvailable,
+ 'video-disabled': this.isVideoAllowed && this.model.attributes.videoAvailable && !this.model.attributes.videoEnabled,
+ 'no-video-available': !this.isVideoAllowed || !this.model.attributes.videoAvailable,
}
},
@@ -358,6 +383,10 @@ export default {
},
videoButtonTooltip() {
+ if (!this.isVideoAllowed) {
+ return t('spreed', 'You are not allowed to enable video')
+ }
+
if (!this.model.attributes.videoAvailable) {
return t('spreed', 'No camera')
}
@@ -391,15 +420,24 @@ export default {
screenSharingButtonClass() {
return {
- 'screensharing-disabled': !this.model.attributes.localScreen,
+ 'screensharing-disabled': this.isScreensharingAllowed && !this.model.attributes.localScreen,
+ 'no-screensharing-available': !this.isScreensharingAllowed,
}
},
screenSharingButtonTooltip() {
+ if (!this.isScreensharingAllowed) {
+ return t('spreed', 'You are not allowed to enable screensharing')
+ }
+
if (this.screenSharingMenuOpen) {
return null
}
+ if (!this.isScreensharingAllowed) {
+ return t('spreed', 'No screensharing')
+ }
+
return this.model.attributes.localScreen ? t('spreed', 'Screensharing options') : t('spreed', 'Enable screensharing')
},
@@ -614,6 +652,10 @@ export default {
},
toggleScreenSharingMenu() {
+ if (!this.isScreensharingAllowed) {
+ return
+ }
+
if (!this.model.getWebRtc().capabilities.supportScreenSharing) {
if (window.location.protocol === 'https:') {
showMessage(t('spreed', 'Screen sharing is not supported by your browser.'))
@@ -746,7 +788,7 @@ export default {
.buttons-bar button.audio-disabled:not(.no-audio-available),
.buttons-bar button.video-disabled:not(.no-video-available),
-.buttons-bar button.screensharing-disabled,
+.buttons-bar button.screensharing-disabled:not(.no-screensharing-available),
.buttons-bar button.lower-hand {
&:hover,
&:focus {
@@ -755,7 +797,8 @@ export default {
}
.buttons-bar button.no-audio-available,
-.buttons-bar button.no-video-available {
+.buttons-bar button.no-video-available,
+.buttons-bar button.no-screensharing-available {
&, & * {
opacity: .7;
cursor: not-allowed;
@@ -763,7 +806,8 @@ export default {
}
.buttons-bar button.no-audio-available:active,
-.buttons-bar button.no-video-available:active {
+.buttons-bar button.no-video-available:active,
+.buttons-bar button.no-screensharing-available:active {
background-color: transparent;
}
diff --git a/src/components/CallView/shared/LocalVideo.vue b/src/components/CallView/shared/LocalVideo.vue
index 534d192bb..8886f8959 100644
--- a/src/components/CallView/shared/LocalVideo.vue
+++ b/src/components/CallView/shared/LocalVideo.vue
@@ -53,6 +53,7 @@
<LocalMediaControls
v-if="showControls"
class="local-media-controls"
+ :token="token"
:model="localMediaModel"
:local-call-participant-model="localCallParticipantModel"
:screen-sharing-button-hidden="isSidebar"
@@ -97,6 +98,10 @@ export default {
mixins: [video],
props: {
+ token: {
+ type: String,
+ required: true,
+ },
localMediaModel: {
type: Object,
required: true,
diff --git a/src/components/Description/Description.vue b/src/components/Description/Description.vue
index 300b91980..1d709c6b2 100644
--- a/src/components/Description/Description.vue
+++ b/src/components/Description/Description.vue
@@ -38,7 +38,7 @@
<template v-if="editing">
<button
class="nc-button nc-button__main description__action"
- :aria-label="t('spreed','Cancel editing description')"
+ :aria-label="t('spreed', 'Cancel editing description')"
@click="handleCancelEditing">
<Close
decorative
@@ -47,7 +47,7 @@
</button>
<button
class="nc-button nc-button__main primary description__action"
- :aria-label="t('spreed','Submit conversation description')"
+ :aria-label="t('spreed', 'Submit conversation description')"
:disabled="!canSubmit"
@click="handleSubmitDescription">
<Check
@@ -65,7 +65,7 @@
</template>
<button v-if="!editing && editable"
class="nc-button nc-button__main"
- :aria-label="t('spreed','Edit conversation description')"
+ :aria-label="t('spreed', 'Edit conversation description')"
@click="handleEditDescription">
<Pencil
decorative
diff --git a/src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue b/src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue
index 647b1772f..df32e225c 100644
--- a/src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue
+++ b/src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue
@@ -22,10 +22,10 @@
<template>
<div class="wrapper">
<button slot="trigger"
- v-tooltip.bottom="t('spreed','Create a new group conversation')"
+ v-tooltip.bottom="t('spreed', 'Create a new group conversation')"
class="toggle"
icon=""
- :aria-label="t('spreed','Create a new group conversation')"
+ :aria-label="t('spreed', 'Create a new group conversation')"
@click="showModal">
<Plus
decorative
diff --git a/src/components/MessagesList/MessagesGroup/Message/Message.vue b/src/components/MessagesList/MessagesGroup/Message/Message.vue
index 6671b01ad..93065ed19 100644
--- a/src/components/MessagesList/MessagesGroup/Message/Message.vue
+++ b/src/components/MessagesList/MessagesGroup/Message/Message.vue
@@ -151,6 +151,12 @@ the main body of the message as well as a quote.
@click.stop="handleMarkAsUnread">
{{ t('spreed', 'Mark as unread') }}
</ActionButton>
+ <ActionLink
+ v-if="linkToFile"
+ icon="icon-text"
+ :href="linkToFile">
+ {{ t('spreed', 'Go to file') }}
+ </ActionLink>
<ActionSeparator v-if="messageActions.length > 0" />
<template
v-for="action in messageActions">
@@ -187,6 +193,7 @@ the main body of the message as well as a quote.
<script>
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
+import ActionLink from '@nextcloud/vue/dist/Components/ActionLink'
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionSeparator from '@nextcloud/vue/dist/Components/ActionSeparator'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
@@ -226,6 +233,7 @@ export default {
components: {
Actions,
ActionButton,
+ ActionLink,
CallButton,
Quote,
RichText,
@@ -562,18 +570,30 @@ export default {
&& this.actorType === this.$store.getters.getActorType()
},
+ isFileShare() {
+ return this.message === '{file}' && this.messageParameters?.file
+ },
+
+ linkToFile() {
+ if (this.isFileShare) {
+ return this.messageParameters?.file?.link
+ }
+ return ''
+ },
+
isDeleteable() {
if (this.isConversationReadOnly) {
return false
}
- const isFileShare = this.message === '{file}'
- && this.messageParameters?.file
+ const isObjectShare = this.message === '{object}'
+ && this.messageParameters?.object
return (moment(this.timestamp * 1000).add(6, 'h')) > moment()
&& this.messageType === 'comment'
&& !this.isDeleting
- && !isFileShare
+ && !this.isFileShare
+ && !isObjectShare
&& (this.isMyMsg
|| (this.conversation.type !== CONVERSATION.TYPE.ONE_TO_ONE
&& (this.participant.participantType === PARTICIPANT.TYPE.OWNER
diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/AudioPlayer.vue b/src/components/MessagesList/MessagesGroup/Message/MessagePart/AudioPlayer.vue
new file mode 100644
index 000000000..64540cf1d
--- /dev/null
+++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/AudioPlayer.vue
@@ -0,0 +1,82 @@
+<!--
+ - @copyright Copyright (c) 2021 Marco Ambrosini <marcoambrosini@pm.me>
+ -
+ - @author Marco Ambrosini <marcoambrosini@pm.me>
+ -
+ - @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>
+ <div class="wrapper">
+ <audio
+ controls
+ :src="fileURL">
+ {{ t('spreed', 'Your browser does not support playing audio files') }}
+ </audio>
+ </div>
+</template>
+
+<script>
+import { generateRemoteUrl } from '@nextcloud/router'
+import { encodePath } from '@nextcloud/paths'
+
+export default {
+ name: 'AudioPlayer',
+
+ props: {
+ /**
+ * File name
+ */
+ name: {
+ type: String,
+ required: true,
+ },
+ link: {
+ type: String,
+ required: true,
+ },
+ /**
+ * File path relative to the user's home storage,
+ * or link share root, includes the file name.
+ */
+ path: {
+ type: String,
+ required: true,
+ },
+ },
+
+ computed: {
+ internalAbsolutePath() {
+ if (this.path.startsWith('/')) {
+ return this.path
+ }
+ return '/' + this.path
+ },
+
+ fileURL() {
+ const userId = this.$store.getters.getUserId()
+ if (userId === null) {
+ // guest mode, use public link download URL
+ return this.link + '/download/' + encodePath(this.name)
+ } else {
+ // use direct DAV URL
+ return generateRemoteUrl(`dav/files/${userId}`) + encodePath(this.internalAbsolutePath)
+ }
+ },
+ },
+
+}
+</script>
diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue b/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue
index b013d62d0..162f1e3b7 100644
--- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue
+++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue
@@ -26,7 +26,7 @@
:tabindex="wrapperTabIndex"
class="file-preview"
:class="{ 'file-preview--viewer-available': isViewerAvailable, 'file-preview--upload-editor': isUploadEditor }"
- @click="handleClick"
+ @click.exact="handleClick"
@keydown.enter="handleClick">
<div
v-if="!isLoading"
@@ -78,6 +78,7 @@ import Close from 'vue-material-design-icons/Close'
import PlayCircleOutline from 'vue-material-design-icons/PlayCircleOutline'
import { getCapabilities } from '@nextcloud/capabilities'
import { encodePath } from '@nextcloud/paths'
+import AudioPlayer from './AudioPlayer'
const PREVIEW_TYPE = {
TEMPORARY: 0,
@@ -236,6 +237,13 @@ export default {
is: 'div',
tag: 'div',
}
+ } else if (this.mimetype.startsWith('audio')) {
+ return {
+ is: AudioPlayer,
+ name: this.name,
+ path: this.path,
+ link: this.link,
+ }
}
return {
is: 'a',
diff --git a/src/components/NewMessageForm/AdvancedInput/AdvancedInput.vue b/src/components/NewMessageForm/AdvancedInput/AdvancedInput.vue
index a0d11281d..222a51846 100644
--- a/src/components/NewMessageForm/AdvancedInput/AdvancedInput.vue
+++ b/src/components/NewMessageForm/AdvancedInput/AdvancedInput.vue
@@ -235,6 +235,7 @@ export default {
}
},
},
+
mounted() {
this.focusInput()
/**
@@ -245,10 +246,12 @@ export default {
this.atWhoPanelExtraClasses = 'talk candidate-mentions'
},
+
beforeDestroy() {
EventBus.$off('routeChange', this.focusInput)
EventBus.$off('focusChatInput', this.focusInput)
},
+
methods: {
onBlur() {
// requires a short delay to avoid blocking click event handlers
@@ -412,10 +415,10 @@ export default {
overflow: visible;
width: 100%;
border:none;
- margin: 0 6px !important;
+ margin: 0 4px !important;
word-break: break-word;
white-space: pre-wrap;
- padding: 8px 16px;
+ padding: 8px 16px 8px 48px;
}
// Support for the placeholder text in the div contenteditable
diff --git a/src/components/NewMessageForm/AudioRecorder/AudioRecorder.vue b/src/components/NewMessageForm/AudioRecorder/AudioRecorder.vue
new file mode 100644
index 000000000..74b31da29
--- /dev/null
+++ b/src/components/NewMessageForm/AudioRecorder/AudioRecorder.vue
@@ -0,0 +1,351 @@
+<!--
+ - @copyright Copyright (c) 2021 Marco Ambrosini <marcoambrosini@pm.me>
+ -
+ - @author Marco Ambrosini <marcoambrosini@pm.me>
+ -
+ - @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>
+ <div
+ class="audio-recorder">
+ <button
+ v-if="!isRecording"
+ v-tooltip.auto="{
+ content: startRecordingTooltip,
+ delay: tooltipDelay,
+ }"
+ class="audio-recorder__trigger nc-button nc-button__main"
+ @click="start">
+ <Microphone
+ :size="16"
+ title=""
+ decorative />
+ </button>
+ <div v-else class="wrapper">
+ <button
+ v-tooltip.auto="{
+ content: abortRecordingTooltip,
+ delay: tooltipDelay,
+ }"
+ class="audio-recorder__stop nc-button nc-button__main"
+ @click="abortRecording">
+ <Close
+ :size="16"
+ title=""
+ decorative />
+ </button>
+ <div class="audio-recorder__info">
+ <div class="recording-indicator fadeOutIn" />
+ <span
+ class="time">
+ {{ parsedRecordTime }}</span>
+ </div>
+ <button
+ v-tooltip.auto="{
+ content: stopRecordingTooltip,
+ delay: tooltipDelay,
+ }"
+ class="audio-recorder__trigger nc-button nc-button__main"
+ :class="{'audio-recorder__trigger--recording': isRecording}"
+ @click="stop">
+ <Check
+ :size="16"
+ title=""
+ decorative />
+ </button>
+ </div>
+ </div>
+</template>
+
+<script>
+import Microphone from 'vue-material-design-icons/Microphone'
+import Close from 'vue-material-design-icons/Close'
+import Check from 'vue-material-design-icons/Check'
+import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
+import { mediaDevicesManager } from '../../../utils/webrtc/index'
+import { showError } from '@nextcloud/dialogs'
+
+export default {
+ name: 'AudioRecorder',
+
+ components: {
+ Microphone,
+ Close,
+ Check,
+ },
+
+ directives: {
+ tooltip: Tooltip,
+ },
+
+ data() {
+ return {
+ // The audio stream object
+ audioStream: null,
+ // The media recorder which generate the recorded chunks
+ mediaRecorder: null,
+ // The chunks array
+ chunks: [],
+ // The final audio file blob
+ blob: null,
+ // The blob url
+ URL: '',
+ // Switched to true if the recording is aborted