diff --git a/css/icons.scss b/css/icons.scss index 6c978004551..1b898f5f237 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 00000000000..25333527ad6 --- /dev/null +++ b/img/folder-multiple-image.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php index c60885bc5bb..de0a0d7b7f1 100644 --- a/lib/Controller/ChatController.php +++ b/lib/Controller/ChatController.php @@ -750,7 +750,7 @@ public function getObjectsSharedInRoomOverview(int $limit = 7): DataResponse { $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 0655686fe0e..09cda5d64f8 100644 --- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue +++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue @@ -25,7 +25,10 @@
-
- {{ fileDetail }} +
+ {{ fileDetail }}
@@ -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 f9e4766f398..9d218b271f9 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" /> - @@ -63,10 +65,6 @@ -
+ + + @@ -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 00000000000..1c1b4c16581 --- /dev/null +++ b/src/components/RightSidebar/SharedItems/SharedItems.vue @@ -0,0 +1,169 @@ + + + + + + + diff --git a/src/components/RightSidebar/SharedItems/SharedItemsTab.vue b/src/components/RightSidebar/SharedItems/SharedItemsTab.vue new file mode 100644 index 00000000000..4dc990b639b --- /dev/null +++ b/src/components/RightSidebar/SharedItems/SharedItemsTab.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/src/services/conversationSharedItemsService.js b/src/services/conversationSharedItemsService.js new file mode 100644 index 00000000000..12922175e75 --- /dev/null +++ b/src/services/conversationSharedItemsService.js @@ -0,0 +1,49 @@ +/** + * @copyright Copyright (c) 2022 Marco Ambrosini + * + * @author Marco Ambrosini + * + * @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 . + * + */ + +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 00000000000..9ea68d9c9cf --- /dev/null +++ b/src/store/conversationSharedItemsStore.js @@ -0,0 +1,100 @@ +/** + * @copyright Copyright (c) 2022 Marco Ambrosini + * + * @author Marco Ambrosini + * + * @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 . + * + */ + +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 8d772fa2683..9f5fbe9c1a1 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: {},