diff options
-rw-r--r-- | src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue | 4 | ||||
-rw-r--r-- | src/components/RightSidebar/SharedItems/SharedItems.vue | 107 | ||||
-rw-r--r-- | src/components/RightSidebar/SharedItems/SharedItemsBrowser/SharedItemsBrowser.vue | 173 | ||||
-rw-r--r-- | src/components/RightSidebar/SharedItems/SharedItemsTab.vue | 85 | ||||
-rw-r--r-- | src/mixins/sharedItems.js | 57 | ||||
-rw-r--r-- | src/services/sharedItemsService.js (renamed from src/services/conversationSharedItemsService.js) | 0 | ||||
-rw-r--r-- | src/store/sharedItemsStore.js (renamed from src/store/conversationSharedItemsStore.js) | 8 | ||||
-rw-r--r-- | src/store/storeConfig.js | 4 |
8 files changed, 330 insertions, 108 deletions
diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue b/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue index 09cda5d64..553b675bd 100644 --- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue +++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/FilePreview.vue @@ -295,7 +295,7 @@ export default { classes += 'preview ' } - if (this.failed || this.previewType === PREVIEW_TYPE.MIME_ICON) { + if (this.failed || this.previewType === PREVIEW_TYPE.MIME_ICON || this.rowLayout) { classes += 'mimeicon' } return classes @@ -323,7 +323,7 @@ export default { if (this.previewType === PREVIEW_TYPE.TEMPORARY) { return this.localUrl } - if (this.previewType === PREVIEW_TYPE.MIME_ICON) { + if (this.previewType === PREVIEW_TYPE.MIME_ICON || this.rowLayout) { return OC.MimeType.getIconUrl(this.mimetype) } // whether to embed/render the file directly diff --git a/src/components/RightSidebar/SharedItems/SharedItems.vue b/src/components/RightSidebar/SharedItems/SharedItems.vue index 072e268e4..d2c848804 100644 --- a/src/components/RightSidebar/SharedItems/SharedItems.vue +++ b/src/components/RightSidebar/SharedItems/SharedItems.vue @@ -20,48 +20,26 @@ --> <template> - <div class="shared-items"> - <AppNavigationCaption :title="title" /> - <div class="files" :class="{'files__list' : isList}"> - <template v-for="file in itemsToDisplay"> - <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 class="shared-items" :class="{'shared-items__list' : isList}"> + <template v-for="file in itemsToDisplay"> + <FilePreview :key="file.id" + :small-preview="isList" + :row-layout="isList" + :is-shared-items-tab="true" + v-bind="file.messageParameters.file" /> + </template> </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' import { SHARED_ITEM } from '../../../constants' export default { name: 'SharedItems', components: { - Button, FilePreview, - DotsHorizontal, - AppNavigationCaption, }, props: { @@ -74,51 +52,17 @@ export default { type: Object, required: true, }, + + // Limits the amount of items displayed + limit: { + type: Boolean, + default: false, + }, }, computed: { itemsToDisplay() { - return Object.values(this.items).reverse().slice(0, 6) - }, - - title() { - switch (this.type) { - case SHARED_ITEM.TYPES.MEDIA: - return t('spreed', 'Media') - case SHARED_ITEM.TYPES.FILE: - return t('spreed', 'Files') - case SHARED_ITEM.TYPES.DECK_CARD: - return t('spreed', 'Deck cards') - case SHARED_ITEM.TYPES.VOICE: - return t('spreed', 'Voice messages') - case SHARED_ITEM.TYPES.LOCATION: - return t('spreed', 'Locations') - case SHARED_ITEM.TYPES.AUDIO: - return t('spreed', 'Audio') - case SHARED_ITEM.TYPES.OTHER: - default: - return t('spreed', 'Other') - } - }, - - buttonTitle() { - switch (this.type) { - case SHARED_ITEM.TYPES.MEDIA: - return t('spreed', 'Show all media') - case SHARED_ITEM.TYPES.FILE: - return t('spreed', 'Show all files') - case SHARED_ITEM.TYPES.DECK_CARD: - return t('spreed', 'Show all deck cards') - case SHARED_ITEM.TYPES.VOICE: - return t('spreed', 'Show all voice messages') - case SHARED_ITEM.TYPES.LOCATION: - return t('spreed', 'Show all locations') - case SHARED_ITEM.TYPES.AUDIO: - return t('spreed', 'Show all audio') - case SHARED_ITEM.TYPES.OTHER: - default: - return t('spreed', 'Show all other') - } + return this.limit ? Object.values(this.items).reverse().slice(0, 6) : Object.values(this.items).reverse() }, isList() { @@ -131,26 +75,16 @@ export 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 { +.shared-items { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 1fr 1fr; + margin-bottom: 16px; grid-gap: 4px; &__list { display: flex; @@ -158,11 +92,4 @@ export default { } } - -.shared-items { - margin-bottom: 16px; - &__more { - margin-top: 8px; - } -} </style> diff --git a/src/components/RightSidebar/SharedItems/SharedItemsBrowser/SharedItemsBrowser.vue b/src/components/RightSidebar/SharedItems/SharedItemsBrowser/SharedItemsBrowser.vue new file mode 100644 index 000000000..f32e4914b --- /dev/null +++ b/src/components/RightSidebar/SharedItems/SharedItemsBrowser/SharedItemsBrowser.vue @@ -0,0 +1,173 @@ +<!-- + - @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> + <Modal size="large" :container="container" v-on="$listeners"> + <div class="shared-items-browser"> + <div class="shared-items-browser__navigation"> + <template v-for="type in sharedItemsOrder"> + <Button v-if="sharedItems[type]" + :key="type" + :class="{'active' : activeTab === type}" + type="tertiary" + @click="handleTabClick(type)"> + {{ getTitle(type) }} + </Button> + </template> + </div> + <div ref="scroller" class="shared-items-browser__content" @scroll="debounceHandleScroll"> + <SharedItems :type="activeTab" + :items="sharedItems[activeTab]" /> + </div> + </div> + </Modal> +</template> + +<script> +import Modal from '@nextcloud/vue/dist/Components/Modal' +import Button from '@nextcloud/vue/dist/Components/Button' +import SharedItems from '../SharedItems.vue' +import sharedItems from '../../../../mixins/sharedItems' +import debounce from 'debounce' + +export default { + name: 'SharedItemsBrowser', + + components: { + Modal, + Button, + SharedItems, + }, + + mixins: [sharedItems], + + props: { + sharedItems: { + type: Object, + required: true, + }, + + activeTab: { + type: String, + required: true, + }, + }, + + data() { + return { + firstItemsLoaded: {}, + isRequestingMoreItems: {}, + hasFetchedAllItems: {}, + } + }, + + computed: { + scroller() { + return this.$refs.scroller + }, + + token() { + return this.$store.getters.getToken() + }, + + container() { + return this.$store.getters.getMainContainerSelector() + }, + }, + + watch: { + activeTab(newType) { + this.firstFetchItems(newType) + }, + }, + + mounted() { + this.firstFetchItems(this.activeTab) + }, + + methods: { + handleTabClick(type) { + this.$emit('update:active-tab', type) + }, + + firstFetchItems(type) { + if (!this.firstItemsLoaded?.[type]) { + this.fetchItems(type) + this.firstItemsLoaded[type] = true + } + }, + + fetchItems(type) { + this.isRequestingMoreItems[this.activeTab] = true + const hasMoreItems = this.$store.dispatch('getSharedItems', { + token: this.token, + type, + }) + if (hasMoreItems === false) { + this.hasFetchedAllItems[this.activeTab] = true + } + this.isRequestingMoreItems[this.activeTab] = false + }, + + debounceHandleScroll: debounce(function() { + this.handleScroll() + }, 50), + + async handleScroll() { + const scrollHeight = this.scroller.scrollHeight + const scrollTop = this.scroller.scrollTop + const containerHeight = this.scroller.clientHeight + if ((scrollHeight - scrollTop - containerHeight < 300) && !this.isRequestingMoreItems?.[this.activeTab] && !this.hasFetchedAllItems?.[this.activeTab]) { + this.fetchItems(this.activeTab) + } + }, + }, +} +</script> + +<style lang="scss" scoped> +.shared-items-browser { + width: 100%; + height: 100%; + position:relative; + display: flex; + flex-direction: column; + &__navigation { + display: flex; + gap: 8px; + padding: 16px; + flex-wrap: wrap; + justify-content: center; + } + &__content { + overflow-y: auto; + overflow-x: hidden; + margin: 0 12px; + } +} + +::v-deep .button-vue { + border-radius: var(--border-radius-large); + &.active { + background-color: var(--color-primary-element-hover); + } +} +</style> diff --git a/src/components/RightSidebar/SharedItems/SharedItemsTab.vue b/src/components/RightSidebar/SharedItems/SharedItemsTab.vue index d3ee8db57..6403ecc0f 100644 --- a/src/components/RightSidebar/SharedItems/SharedItemsTab.vue +++ b/src/components/RightSidebar/SharedItems/SharedItemsTab.vue @@ -22,16 +22,34 @@ <template> <div v-if="!loading && active"> <template v-for="type in sharedItemsOrder"> - <SharedItems v-if="sharedItems[type]" - :key="type" - :type="type" - :items="sharedItems[type]" /> + <div v-if="sharedItems[type]" :key="type"> + <AppNavigationCaption :title="getTitle(type)" /> + <SharedItems :type="type" + :limit="true" + :items="sharedItems[type]" /> + <Button v-if="hasMore(sharedItems[type])" + type="tertiary-no-background" + class="more" + :wide="true" + @click="showMore(type)"> + <template #icon> + <DotsHorizontal :size="20" + decorative + title="" /> + </template> + {{ getButtonTitle(type) }} + </Button> + </div> </template> <AppNavigationCaption :title="t('spreed', 'Projects')" /> <CollectionList v-if="getUserId && token" :id="token" type="room" :name="conversation.displayName" /> + <SharedItemsBrowser v-if="showSharedItemsBrowser" + :shared-items="sharedItems" + :active-tab.sync="browserActiveTab" + @close="showSharedItemsBrowser = false" /> </div> </template> @@ -40,6 +58,10 @@ import { CollectionList } from 'nextcloud-vue-collections' import SharedItems from './SharedItems' import { SHARED_ITEM } from '../../../constants' import AppNavigationCaption from '@nextcloud/vue/dist/Components/AppNavigationCaption' +import SharedItemsBrowser from './SharedItemsBrowser/SharedItemsBrowser.vue' +import DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue' +import Button from '@nextcloud/vue/dist/Components/Button' +import sharedItems from '../../../mixins/sharedItems' export default { @@ -49,8 +71,13 @@ export default { SharedItems, CollectionList, AppNavigationCaption, + SharedItemsBrowser, + DotsHorizontal, + Button, }, + mixins: [sharedItems], + props: { active: { @@ -59,6 +86,13 @@ export default { }, }, + data() { + return { + showSharedItemsBrowser: false, + browserActiveTab: '', + } + }, + computed: { getUserId() { return this.$store.getters.getUserId() @@ -79,12 +113,6 @@ export default { sharedItems() { return this.$store.getters.sharedItems(this.token) }, - - // Defines the order of the sections - sharedItemsOrder() { - // FIXME restore when non files work return [SHARED_ITEM.TYPES.MEDIA, SHARED_ITEM.TYPES.FILE, SHARED_ITEM.TYPES.VOICE, SHARED_ITEM.TYPES.AUDIO, SHARED_ITEM.TYPES.LOCATION, SHARED_ITEM.TYPES.DECK_CARD, SHARED_ITEM.TYPES.OTHER] - return [SHARED_ITEM.TYPES.MEDIA, SHARED_ITEM.TYPES.FILE, SHARED_ITEM.TYPES.VOICE, SHARED_ITEM.TYPES.AUDIO] - }, }, watch: { @@ -99,6 +127,43 @@ export default { getSharedItemsOverview() { this.$store.dispatch('getSharedItemsOverview', { token: this.token }) }, + + hasMore(items) { + return Object.values(items).length > 6 + }, + + showMore(type) { + this.browserActiveTab = type + this.showSharedItemsBrowser = true + }, + + getButtonTitle(type) { + switch (type) { + case SHARED_ITEM.TYPES.MEDIA: + return t('spreed', 'Show all media') + case SHARED_ITEM.TYPES.FILE: + return t('spreed', 'Show all files') + case SHARED_ITEM.TYPES.DECK_CARD: + return t('spreed', 'Show all deck cards') + case SHARED_ITEM.TYPES.VOICE: + return t('spreed', 'Show all voice messages') + case SHARED_ITEM.TYPES.LOCATION: + return t('spreed', 'Show all locations') + case SHARED_ITEM.TYPES.AUDIO: + return t('spreed', 'Show all audio') + case SHARED_ITEM.TYPES.OTHER: + default: + return t('spreed', 'Show all other') + } + }, }, } </script> + +<style lang="scss" scoped> + +.more { + margin-top: 8px; +} + +</style> diff --git a/src/mixins/sharedItems.js b/src/mixins/sharedItems.js new file mode 100644 index 000000000..5569733ae --- /dev/null +++ b/src/mixins/sharedItems.js @@ -0,0 +1,57 @@ +/** + * @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 { SHARED_ITEM } from '../constants' + +const sharedItems = { + computed: { + // Defines the order of the sections + sharedItemsOrder() { + // FIXME restore when non files work return [SHARED_ITEM.TYPES.MEDIA, SHARED_ITEM.TYPES.FILE, SHARED_ITEM.TYPES.VOICE, SHARED_ITEM.TYPES.AUDIO, SHARED_ITEM.TYPES.LOCATION, SHARED_ITEM.TYPES.DECK_CARD, SHARED_ITEM.TYPES.OTHER] + return [SHARED_ITEM.TYPES.MEDIA, SHARED_ITEM.TYPES.FILE, SHARED_ITEM.TYPES.VOICE, SHARED_ITEM.TYPES.AUDIO] + }, + }, + + methods: { + getTitle(type) { + switch (type) { + case SHARED_ITEM.TYPES.MEDIA: + return t('spreed', 'Media') + case SHARED_ITEM.TYPES.FILE: + return t('spreed', 'Files') + case SHARED_ITEM.TYPES.DECK_CARD: + return t('spreed', 'Deck cards') + case SHARED_ITEM.TYPES.VOICE: + return t('spreed', 'Voice messages') + case SHARED_ITEM.TYPES.LOCATION: + return t('spreed', 'Locations') + case SHARED_ITEM.TYPES.AUDIO: + return t('spreed', 'Audio') + case SHARED_ITEM.TYPES.OTHER: + default: + return t('spreed', 'Other') + } + }, + }, +} + +export default sharedItems diff --git a/src/services/conversationSharedItemsService.js b/src/services/sharedItemsService.js index a0537ecf9..a0537ecf9 100644 --- a/src/services/conversationSharedItemsService.js +++ b/src/services/sharedItemsService.js diff --git a/src/store/conversationSharedItemsStore.js b/src/store/sharedItemsStore.js index 9242dd0c8..b5ce88754 100644 --- a/src/store/conversationSharedItemsStore.js +++ b/src/store/sharedItemsStore.js @@ -21,7 +21,7 @@ */ import Vue from 'vue' -import { getSharedItemsOverview, getSharedItems } from '../services/conversationSharedItemsService' +import { getSharedItemsOverview, getSharedItems } from '../services/sharedItemsService' import { SHARED_ITEM } from '../constants' const getItemTypeFromMessage = function(message) { @@ -120,19 +120,19 @@ const actions = { return false } - const limit = 100 + const limit = 20 const lastKnownMessageId = Math.min.apply(Math, Object.keys(state.sharedItemsByConversationAndType[token][type])) try { const response = await getSharedItems(token, type, lastKnownMessageId, limit) const messages = response.data.ocs.data const hasMore = messages.length >= limit - // loop over the response elements and add them to the store for (const message in messages) { + commit('addSharedItemMessage', { token, type, - message, + message: messages[message], }) } return hasMore diff --git a/src/store/storeConfig.js b/src/store/storeConfig.js index 9f5fbe9c1..121f3cdcf 100644 --- a/src/store/storeConfig.js +++ b/src/store/storeConfig.js @@ -39,7 +39,7 @@ import uiModeStore from './uiModeStore' import windowVisibilityStore from './windowVisibilityStore' import messageActionsStore from './messageActionsStore' import reactionsStore from './reactionsStore' -import conversationSharedItemStore from './conversationSharedItemsStore' +import sharedItemStore from './sharedItemsStore' export default { modules: { @@ -62,7 +62,7 @@ export default { windowVisibilityStore, messageActionsStore, reactionsStore, - conversationSharedItemStore, + sharedItemStore, }, mutations: {}, |