Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/spreed.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoas Schilling <213943+nickvergessen@users.noreply.github.com>2022-04-13 18:03:32 +0300
committerGitHub <noreply@github.com>2022-04-13 18:03:32 +0300
commita36cd1d4833e76bc3052fae5c9f3041d793c477b (patch)
tree7ec0f872a1c85a1b3e73a8402d448c4e748a7058
parenta0b760543daf96b9f232a96145a0351cbb888f6f (diff)
parenta7762280b815c2b51ffdded9dab101fa39939e7f (diff)
Merge pull request #7076 from nextcloud/feature/1577/conversation-shared-items
🗃️ Shared Items tab
-rw-r--r--css/icons.scss1
-rw-r--r--img/folder-multiple-image.svg3
-rw-r--r--lib/Controller/ChatController.php2
-rw-r--r--src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue66
-rw-r--r--src/components/RightSidebar/RightSidebar.vue26
-rw-r--r--src/components/RightSidebar/SharedItems/SharedItems.vue169
-rw-r--r--src/components/RightSidebar/SharedItems/SharedItemsTab.vue103
-rw-r--r--src/services/conversationSharedItemsService.js49
-rw-r--r--src/store/conversationSharedItemsStore.js100
-rw-r--r--src/store/storeConfig.js2
10 files changed, 496 insertions, 25 deletions
diff --git a/css/icons.scss b/css/icons.scss
index 6c9780045..1b898f5f2 100644
--- a/css/icons.scss
+++ b/css/icons.scss
@@ -10,6 +10,7 @@
@include icon-black-white('text', 'filetypes', 1, true);
@include icon-black-white('promoted-view', 'spreed', 1);
@include icon-black-white('grid-view', 'spreed', 1);
+@include icon-black-white('folder-multiple-image', 'spreed', 1);
.app-talk,
.talk-modal,
diff --git a/img/folder-multiple-image.svg b/img/folder-multiple-image.svg
new file mode 100644
index 000000000..25333527a
--- /dev/null
+++ b/img/folder-multiple-image.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M7,15L11.5,9L15,13.5L17.5,10.5L21,15M22,4H14L12,2H6A2,2 0 0,0 4,4V16A2,2 0 0,0 6,18H22A2,2 0 0,0 24,16V6A2,2 0 0,0 22,4M2,6H0V11H0V20A2,2 0 0,0 2,22H20V20H2V6Z" />
+</svg> \ No newline at end of file
diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php
index c60885bc5..de0a0d7b7 100644
--- a/lib/Controller/ChatController.php
+++ b/lib/Controller/ChatController.php
@@ -750,7 +750,7 @@ class ChatController extends AEnvironmentAwareController {
$attachments = $this->attachmentService->getAttachmentsByType($this->room, $objectType, 0, $limit);
$messageIdsByType[$objectType] = array_map(static fn (Attachment $attachment): int => $attachment->getMessageId(), $attachments);
}
- $comments = $this->chatManager->getMessagesById($this->room, array_merge(...$messageIdsByType));
+ $comments = $this->chatManager->getMessagesById($this->room, array_merge(...array_values($messageIdsByType)));
foreach ($comments as $comment) {
$message = $this->messageParser->createMessage($this->room, $this->participant, $comment, $this->l);
diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue b/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue
index 0655686fe..09cda5d64 100644
--- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue
+++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue
@@ -25,7 +25,10 @@
<file-preview v-bind="filePreview"
:tabindex="wrapperTabIndex"
class="file-preview"
- :class="{ 'file-preview--viewer-available': isViewerAvailable, 'file-preview--upload-editor': isUploadEditor }"
+ :class="{ 'file-preview--viewer-available': isViewerAvailable,
+ 'file-preview--upload-editor': isUploadEditor,
+ 'file-preview--shared-items-grid': isSharedItemsTab && !rowLayout,
+ 'file-preview--row-layout': rowLayout }"
@click.exact="handleClick"
@keydown.enter="handleClick">
<div v-if="!isLoading"
@@ -63,8 +66,8 @@
</template>
</Button>
<ProgressBar v-if="isTemporaryUpload && !isUploadEditor" :value="uploadProgress" />
- <div class="name-container">
- <strong v-if="shouldShowFileDetail">{{ fileDetail }}</strong>
+ <div v-if="shouldShowFileDetail" class="name-container">
+ {{ fileDetail }}
</div>
</file-preview>
</template>
@@ -203,6 +206,16 @@ export default {
type: Boolean,
default: false,
},
+
+ rowLayout: {
+ type: Boolean,
+ default: false,
+ },
+
+ isSharedItemsTab: {
+ type: Boolean,
+ default: false,
+ },
},
data() {
return {
@@ -212,6 +225,9 @@ export default {
},
computed: {
shouldShowFileDetail() {
+ if (this.isSharedItemsTab && !this.rowLayout) {
+ return false
+ }
// display the file detail below the preview if the preview
// is not easily recognizable, when:
return (
@@ -473,6 +489,7 @@ export default {
.file-preview {
position: relative;
+ min-width: 0;
width: 100%;
/* The file preview can not be a block; otherwise it would fill the whole
width of the container and the loading icon would not be centered on the
@@ -522,8 +539,8 @@ export default {
}
.image-container {
- display: inline-block;
- position: relative;
+ display: flex;
+ height: 100%;
&.playable {
.preview {
@@ -554,19 +571,11 @@ export default {
}
.name-container {
- /* Ellipsis with 100% width */
- display: table;
- table-layout: fixed;
+ font-weight: bold;
width: 100%;
-
- strong {
- /* As the file preview is an inline block the name is set as a block to
- force it to be on its own line below the preview. */
- display: block;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- }
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
}
&:not(.file-preview--viewer-available) {
@@ -589,6 +598,29 @@ export default {
width: 100%;
}
}
+
+ &--row-layout {
+ display: flex;
+ align-items: center;
+ height: 36px;
+ border-radius: var(--border-radius);
+ padding: 2px 4px;
+
+ .image-container {
+ height: 100%;
+ }
+
+ .name-container {
+ padding: 0 4px;
+ }
+ }
+
+ &--shared-items-grid {
+ aspect-ratio: 1;
+ .preview {
+ width: 100%;
+ }
+ }
}
.remove-file {
diff --git a/src/components/RightSidebar/RightSidebar.vue b/src/components/RightSidebar/RightSidebar.vue
index f9e4766f3..9d218b271 100644
--- a/src/components/RightSidebar/RightSidebar.vue
+++ b/src/components/RightSidebar/RightSidebar.vue
@@ -26,6 +26,7 @@
:title="title"
:title-tooltip="title"
:starred="isFavorited"
+ :active="activeTab"
:title-editable="canModerate && isRenamingConversation"
:class="'active-tab-' + activeTab"
@update:active="handleUpdateActive"
@@ -55,7 +56,8 @@
:can-search="canSearchParticipants"
:can-add="canAddParticipants" />
</AppSidebarTab>
- <AppSidebarTab id="details-tab"
+ <AppSidebarTab v-if="!getUserId || showSIPSettings"
+ id="details-tab"
:order="3"
:name="t('spreed', 'Details')"
icon="icon-details">
@@ -63,10 +65,6 @@
<SipSettings v-if="showSIPSettings"
:meeting-id="conversation.token"
:attendee-pin="conversation.attendeePin" />
- <CollectionList v-if="getUserId && conversation.token"
- :id="conversation.token"
- type="room"
- :name="conversation.displayName" />
<div v-if="!getUserId" id="app-settings">
<div id="app-settings-header">
<Button type="tertiary" @click="showSettings">
@@ -80,6 +78,14 @@
</div>
</div>
</AppSidebarTab>
+ <AppSidebarTab v-if="getUserId"
+ id="shared-items"
+ ref="sharedItemsTab"
+ :order="4"
+ icon="icon-folder-multiple-image"
+ :name="t('spreed', 'Shared items')">
+ <SharedItemsTab :active="activeTab === 'shared-items'" />
+ </AppSidebarTab>
</AppSidebar>
</template>
@@ -87,8 +93,8 @@
import { emit } from '@nextcloud/event-bus'
import AppSidebar from '@nextcloud/vue/dist/Components/AppSidebar'
import AppSidebarTab from '@nextcloud/vue/dist/Components/AppSidebarTab'
+import SharedItemsTab from './SharedItems/SharedItemsTab'
import ChatView from '../ChatView'
-import { CollectionList } from 'nextcloud-vue-collections'
import BrowserStorage from '../../services/BrowserStorage'
import { CONVERSATION, WEBINAR, PARTICIPANT } from '../../constants'
import ParticipantsTab from './Participants/ParticipantsTab'
@@ -104,8 +110,8 @@ export default {
components: {
AppSidebar,
AppSidebarTab,
+ SharedItemsTab,
ChatView,
- CollectionList,
ParticipantsTab,
SetGuestUsername,
SipSettings,
@@ -226,6 +232,12 @@ export default {
if (!this.isRenamingConversation) {
this.conversationName = this.conversation.displayName
}
+
+ if (this.isOneToOne) {
+ this.activeTab = 'shared-items'
+ } else {
+ this.activeTab = 'participants'
+ }
},
token() {
diff --git a/src/components/RightSidebar/SharedItems/SharedItems.vue b/src/components/RightSidebar/SharedItems/SharedItems.vue
new file mode 100644
index 000000000..1c1b4c165
--- /dev/null
+++ b/src/components/RightSidebar/SharedItems/SharedItems.vue
@@ -0,0 +1,169 @@
+<!--
+ - @copyright Copyright (c) 2022 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="shared-items">
+ <AppNavigationCaption :title="title" />
+ <div class="files" :class="{'files__list' : isList}">
+ <template v-for="file in filesToDisplay">
+ <FilePreview :key="file.id"
+ :small-preview="isList"
+ :row-layout="isList"
+ :is-shared-items-tab="true"
+ v-bind="file.messageParameters.file" />
+ </template>
+ </div>
+ <Button v-if="hasMore"
+ type="tertiary"
+ class="shared-items__more"
+ :wide="true"
+ @click="handleCaptionClick">
+ <template #icon>
+ <DotsHorizontal :size="20"
+ decorative
+ title="" />
+ </template>
+ {{ buttonTitle }}
+ </Button>
+ </div>
+</template>
+
+<script>
+import Button from '@nextcloud/vue/dist/Components/Button'
+import FilePreview from '../../MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue'
+import DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'
+import AppNavigationCaption from '@nextcloud/vue/dist/Components/AppNavigationCaption'
+import { showMessage } from '@nextcloud/dialogs'
+
+export default {
+ name: 'SharedItems',
+
+ components: {
+ Button,
+ FilePreview,
+ DotsHorizontal,
+ AppNavigationCaption,
+ },
+
+ props: {
+ type: {
+ type: String,
+ required: true,
+ },
+
+ items: {
+ type: Object,
+ required: true,
+ },
+ },
+
+ computed: {
+ filesToDisplay() {
+ return Object.values(this.items).reverse().slice(0, 6)
+ },
+
+ title() {
+ switch (this.type) {
+ case 'media':
+ return t('spreed', 'Media')
+ case 'file':
+ return t('spreed', 'Files')
+ case 'deck-card':
+ return t('spreed', 'Deck cards')
+ case 'voice':
+ return t('spreed', 'Voice messages')
+ case 'location':
+ return t('spreed', 'Locations')
+ case 'audio':
+ return t('spreed', 'Audio')
+ case 'other':
+ return t('spreed', 'Other')
+ default:
+ return ''
+ }
+ },
+
+ buttonTitle() {
+ switch (this.type) {
+ case 'media':
+ return t('spreed', 'Show all media')
+ case 'file':
+ return t('spreed', 'Show all files')
+ case 'deck-card':
+ return t('spreed', 'Show all deck cards')
+ case 'voice':
+ return t('spreed', 'Show all voice messages')
+ case 'location':
+ return t('spreed', 'Show all locations')
+ case 'audio':
+ return t('spreed', 'Show all audio')
+ case 'other':
+ return t('spreed', 'Show all other')
+ default:
+ return ''
+ }
+ },
+
+ isList() {
+ switch (this.type) {
+ case 'media':
+ return false
+ case 'locations':
+ return false
+ default:
+ return true
+ }
+ },
+
+ hasMore() {
+ return Object.values(this.items).length > 6
+ },
+ },
+
+ methods: {
+ handleCaptionClick() {
+ showMessage('Screenshot feature only. Implementation of the real feature will come soon! 😎')
+ console.debug('Show more')
+ },
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+.files {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-template-rows: 1fr 1fr;
+ grid-gap: 4px;
+ &__list {
+ display: flex;
+ flex-direction: column;
+ }
+
+}
+
+.shared-items {
+ margin-bottom: 16px;
+ &__more {
+ margin-top: 8px;
+ }
+}
+</style>
diff --git a/src/components/RightSidebar/SharedItems/SharedItemsTab.vue b/src/components/RightSidebar/SharedItems/SharedItemsTab.vue
new file mode 100644
index 000000000..4dc990b63
--- /dev/null
+++ b/src/components/RightSidebar/SharedItems/SharedItemsTab.vue
@@ -0,0 +1,103 @@
+<!--
+ - @copyright Copyright (c) 2022 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 v-if="!loading && active">
+ <template v-for="type in sharedItemsOrder">
+ <SharedItems v-if="sharedItems[type]"
+ :key="type"
+ :type="type"
+ :items="sharedItems[type]" />
+ </template>
+ <AppNavigationCaption :title="t('spreed', 'Projects')" />
+ <CollectionList v-if="getUserId && token"
+ :id="token"
+ type="room"
+ :name="conversation.displayName" />
+ </div>
+</template>
+
+<script>
+import { CollectionList } from 'nextcloud-vue-collections'
+import SharedItems from './SharedItems'
+import AppNavigationCaption from '@nextcloud/vue/dist/Components/AppNavigationCaption'
+
+export default {
+
+ name: 'SharedItemsTab',
+
+ components: {
+ SharedItems,
+ CollectionList,
+ AppNavigationCaption,
+ },
+
+ props: {
+
+ active: {
+ type: Boolean,
+ required: true,
+ },
+ },
+
+ computed: {
+ getUserId() {
+ return this.$store.getters.getUserId()
+ },
+
+ token() {
+ return this.$store.getters.getToken()
+ },
+
+ conversation() {
+ return this.$store.getters.conversation(this.token)
+ },
+
+ loading() {
+ return !this.sharedItems
+ },
+
+ sharedItems() {
+ return this.$store.getters.sharedItems(this.token)
+ },
+
+ // Defines the order of the sections
+ sharedItemsOrder() {
+ // FIXME restore when non files work return ['media', 'file', 'voice', 'audio', 'location', 'deckcard', 'other']
+ return ['media', 'file', 'voice', 'audio']
+ },
+ },
+
+ watch: {
+ active(newValue) {
+ if (newValue) {
+ this.getSharedItemsOverview()
+ }
+ },
+ },
+
+ methods: {
+ getSharedItemsOverview() {
+ this.$store.dispatch('getSharedItemsOverview', { token: this.token })
+ },
+ },
+}
+</script>
diff --git a/src/services/conversationSharedItemsService.js b/src/services/conversationSharedItemsService.js
new file mode 100644
index 000000000..12922175e
--- /dev/null
+++ b/src/services/conversationSharedItemsService.js
@@ -0,0 +1,49 @@
+/**
+ * @copyright Copyright (c) 2022 Marco Ambrosini <marcoambrosini@pm.me>
+ *
+ * @author Marco Ambrosini <marcoambrosini@pm.me>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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/>.
+ *
+ */
+
+import axios from '@nextcloud/axios'
+import { generateOcsUrl } from '@nextcloud/router'
+
+// Returns the last n shared items for each category and for a given conversation
+// (n = limit)
+const getSharedItemsOverview = async function(token, limit) {
+ return axios.get(generateOcsUrl('apps/spreed/api/v1/chat/{token}/share/overview', {
+ token,
+ limit,
+ }))
+}
+
+// Returns the last 200 (or limit) shared items, given a conversation and the type
+// of shared item
+const getSharedItems = async function(token, objectType, lastKnownMessageId, limit,) {
+ return axios.get(generateOcsUrl('apps/spreed/api/v1/chat/{token}/share', {
+ token,
+ objectType,
+ lastKnownMessageId,
+ limit,
+ }))
+}
+
+export {
+ getSharedItems,
+ getSharedItemsOverview,
+}
diff --git a/src/store/conversationSharedItemsStore.js b/src/store/conversationSharedItemsStore.js
new file mode 100644
index 000000000..9ea68d9c9
--- /dev/null
+++ b/src/store/conversationSharedItemsStore.js
@@ -0,0 +1,100 @@
+/**
+ * @copyright Copyright (c) 2022 Marco Ambrosini <marcoambrosini@pm.me>
+ *
+ * @author Marco Ambrosini <marcoambrosini@pm.me>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * 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/>.
+ *
+ */
+
+import Vue from 'vue'
+import { getSharedItemsOverview, getSharedItems } from '../services/conversationSharedItemsService'
+
+// Store structure
+// token: {
+// media: {},
+// file: {},
+// voice: {},
+// audio: {},
+// location: {}
+// deckcard: {},
+// other: {},
+
+const state = () => ({
+ state: {},
+})
+
+const getters = {
+ sharedItems: state => token => {
+ const sharedItems = {}
+ if (!state[token]) {
+ return {}
+ }
+ for (const type of Object.keys(state[token])) {
+ if (Object.keys(state[token][type]).length !== 0) {
+ sharedItems[type] = state[token][type]
+ }
+ }
+ return sharedItems
+ },
+}
+
+export const mutations = {
+ addSharedItemsOverview: (state, { token, data }) => {
+ if (!state[token]) {
+ Vue.set(state, token, {})
+ }
+ for (const type of Object.keys(data)) {
+ if (!state[token][type]) {
+ Vue.set(state[token], type, {})
+ for (const message of data[type]) {
+ if (!state[token][type]?.[message.id]) {
+ Vue.set(state[token][type], message.id, message)
+ }
+ }
+ }
+ }
+ },
+}
+
+const actions = {
+ async getSharedItems({ commit }, { token, type, lastKnownMessageId, limit }) {
+ try {
+ const response = await getSharedItems(token, type, lastKnownMessageId, limit)
+ // loop over the response elements and add them to the store
+ for (const sharedItem in response) {
+ commit('addSharedItem', sharedItem)
+ }
+
+ } catch (error) {
+ console.debug(error)
+ }
+ },
+
+ async getSharedItemsOverview({ commit }, { token }) {
+ try {
+ const response = await getSharedItemsOverview(token, 10)
+ commit('addSharedItemsOverview', {
+ token,
+ data: response.data.ocs.data,
+ })
+ } catch (error) {
+ console.debug(error)
+ }
+ },
+}
+
+export default { state, mutations, getters, actions }
diff --git a/src/store/storeConfig.js b/src/store/storeConfig.js
index 8d772fa26..9f5fbe9c1 100644
--- a/src/store/storeConfig.js
+++ b/src/store/storeConfig.js
@@ -39,6 +39,7 @@ import uiModeStore from './uiModeStore'
import windowVisibilityStore from './windowVisibilityStore'
import messageActionsStore from './messageActionsStore'
import reactionsStore from './reactionsStore'
+import conversationSharedItemStore from './conversationSharedItemsStore'
export default {
modules: {
@@ -61,6 +62,7 @@ export default {
windowVisibilityStore,
messageActionsStore,
reactionsStore,
+ conversationSharedItemStore,
},
mutations: {},