diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue index 2014b8ad85..915a0b4c51 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/CatalogList.vue @@ -64,6 +64,7 @@ :channelId="item.id" :detailsRouteName="detailsRouteName" style="flex-grow: 1; width: 100%" + @show-channel-details="handleShowChannelDetails" /> @@ -142,6 +143,22 @@ + + + + @@ -156,6 +173,7 @@ import sortBy from 'lodash/sortBy'; import union from 'lodash/union'; import { RouteNames } from '../../constants'; + import ChannelDetailsSidePanel from './ChannelDetailsSidePanel'; import CatalogFilters from './CatalogFilters'; import ChannelItem from './ChannelItem'; import LoadingText from 'shared/views/LoadingText'; @@ -178,6 +196,7 @@ Checkbox, ToolBar, OfflineText, + ChannelDetailsSidePanel, }, mixins: [channelExportMixin, constantsTranslationMixin], data() { @@ -185,12 +204,8 @@ loading: true, loadError: false, selecting: false, - - /** - * jayoshih: router guard makes it difficult to track - * differences between previous query params and new - * query params, so just track it manually - */ + showSidePanel: false, + selectedChannelId: null, previousQuery: this.$route.query, /** @@ -301,6 +316,14 @@ this.setSelection(false); return this.downloadChannelsPDF(params); }, + handleShowChannelDetails(channelId) { + this.showSidePanel = true; + this.selectedChannelId = channelId; + }, + handleCloseSidePanel() { + this.showSidePanel = false; + this.selectedChannelId = null; + }, }, $trs: { resultsText: '{count, plural,\n =1 {# result found}\n other {# results found}}', @@ -326,4 +349,41 @@ margin: 0 auto; } + .backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 9; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.7); + background-attachment: fixed; + } + + .backdrop-enter { + opacity: 0; + } + + .backdrop-enter-to { + opacity: 1; + } + + .backdrop-enter-active { + transition: opacity 0.2s ease-in-out; + } + + .backdrop-leave { + opacity: 1; + } + + .backdrop-leave-to { + opacity: 0; + } + + .backdrop-leave-active { + transition: opacity 0.2s ease-in-out; + } + diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelDetailsSidePanel.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelDetailsSidePanel.vue new file mode 100644 index 0000000000..c54292c438 --- /dev/null +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelDetailsSidePanel.vue @@ -0,0 +1,152 @@ + + + + + + + + {{ channel ? channel.name : '' }} + + + + + + + + + + + + + + + + + + diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelItem.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelItem.vue index c4fb7abf95..c83842bf44 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelItem.vue +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelItem.vue @@ -7,9 +7,9 @@ :class="{ added }" data-test="channel-card" tabindex="0" - :href="linkToChannelTree ? channelHref : null" - :to="linkToChannelTree ? null : channelDetailsLink" - @click="goToChannelRoute" + :href="isInChannelList && linkToChannelTree ? channelHref : null" + :to="isInChannelList ? (linkToChannelTree ? null : channelDetailsLink) : null" + @click="handleChannelClick" > - + + this.channel.last_published; + }, + filteredIcons() { + return this.iconConfigs + .filter(c => ['copy', 'open-tab-link'].includes(c.key)) + .filter(c => c.show); + }, + detailsIcon() { + return this.iconConfigs.find(c => c.key === 'details' && c.show); + }, + iconConfigs() { + return [ + { + key: 'copy', + show: !this.allowEdit && this.channel.published, + icon: 'copy', + tooltip: this.$tr('copyToken'), + dataTest: 'token-button', + clickHandler: () => { + this.tokenDialog = true; + }, + }, + // show open tab link when + // only one of source or demo server url is present + // we do not show this icon if we are in channel list + { + key: 'open-tab-link', + show: + !this.isInChannelList && + (this.channel.demo_server_url || this.channel.source_url) && + ((this.loggedIn && !this.channel.demo_server_url && this.channel.source_url) || + (this.loggedIn && !this.channel.source_url && this.channel.demo_server_url) || + (!this.loggedIn && + this.channel.published && + // this check ensures that we show link to both in kebab menu + !(this.channel.source_url && this.channel.demo_server_url))), + icon: 'openNewTab', + tooltip: this.channel.demo_server_url + ? this.$tr('viewOnKolibri') + : this.$tr('goToWebsite'), + dataTest: 'view-on-kolibri', + clickHandler: () => { + this.viewOnKolibri(); + }, + }, + { + key: 'details', + show: !this.libraryMode && this.loggedIn, + icon: 'info', + tooltip: this.$tr('details'), + dataTest: 'details-button', + to: this.channelDetailsLink, + }, + // show kebab menu when + // we are listing channels + // or when both source and demo server urls are present and user is logged in + { + key: 'kebab-menu', + show: + this.isInChannelList || + (this.loggedIn && this.channel.source_url && this.channel.demo_server_url), + icon: 'optionsVertical', + dataTest: 'menu', + }, + ]; }, linkToChannelTree() { return this.loggedIn && !this.libraryMode; @@ -345,8 +411,9 @@ return false; } }, - hasUnpublishedChanges() { - return !this.channel.last_published || this.channel.modified > this.channel.last_published; + showKebabMenu() { + const kebabConfig = this.iconConfigs.find(config => config.key === 'kebab-menu'); + return Boolean(kebabConfig && kebabConfig.show); }, }, mounted() { @@ -375,16 +442,21 @@ }); } }, - goToChannelRoute() { - this.linkToChannelTree - ? (window.location.href = this.channelHref) - : this.$router.push(this.channelDetailsLink).catch(() => {}); + handleChannelClick() { + if (!this.isInChannelList) { + this.$emit('show-channel-details', this.channelId); + } }, trackTokenCopy() { this.$analytics.trackAction('channel_list', 'Copy token', { eventLabel: this.channel.primary_token, }); }, + viewOnKolibri() { + if (this.channel.demo_server_url) { + window.open(this.channel.demo_server_url, '_blank'); + } + }, }, $trs: { resourceCount: '{count, plural,\n =1 {# resource}\n other {# resources}}', @@ -408,6 +480,7 @@ channelRemovedSnackbar: 'Channel removed', channelLanguageNotSetIndicator: 'No language set', cancel: 'Cancel', + viewOnKolibri: 'View on Kolibri', }, }; diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelList.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelList.vue index eba4dd2032..6b4e7a53cc 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelList.vue +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/ChannelList.vue @@ -55,6 +55,7 @@ v-for="channel in listChannels" :key="channel.id" :channelId="channel.id" + :isInChannelList="true" allowEdit fullWidth /> diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/channelItem.spec.js b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/channelItem.spec.js index bee20bfd13..8c4a9fb5c7 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/channelItem.spec.js +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/__tests__/channelItem.spec.js @@ -24,6 +24,7 @@ function makeWrapper(allowEdit, deleteStub, libraryMode) { propsData: { channelId: channelId, allowEdit: allowEdit, + isInChannelList: true, }, computed: { libraryMode() { diff --git a/contentcuration/contentcuration/frontend/shared/views/details/DetailsPanel.vue b/contentcuration/contentcuration/frontend/shared/views/details/DetailsPanel.vue index 9db35d0376..adbd257548 100644 --- a/contentcuration/contentcuration/frontend/shared/views/details/DetailsPanel.vue +++ b/contentcuration/contentcuration/frontend/shared/views/details/DetailsPanel.vue @@ -1,27 +1,104 @@ - - - - - - {{ isChannel ? _details.name : _details.title }} - - - {{ _details.description }} - + + + {{ _details.description }} + + + + + + + + + + + {{ $tr('viewInKolibri') }} + + + {{ $tr('goToChannel') }} + + + + + {{ loggedIn ? $tr('options') : $tr('downloadButton') }} + + + + + + + {{ $tr('goToWebsite') }} + + + {{ $tr('viewInKolibri') }} + + + + {{ $tr('downloadPDF') }} + + + {{ $tr('downloadCSV') }} + + + + + + import { mapGetters } from 'vuex'; import cloneDeep from 'lodash/cloneDeep'; import defaultsDeep from 'lodash/defaultsDeep'; import camelCase from 'lodash/camelCase'; import orderBy from 'lodash/orderBy'; + import BaseMenu from '../BaseMenu.vue'; import { SCALE_TEXT, SCALE, CHANNEL_SIZE_DIVISOR } from './constants'; import DetailsRow from './DetailsRow'; import { CategoriesLookup, LevelsLookup } from 'shared/constants'; @@ -419,6 +498,7 @@ CopyToken, DetailsRow, Thumbnail, + BaseMenu, }, mixins: [ fileSizeMixin, @@ -428,10 +508,6 @@ metadataTranslationMixin, ], props: { - // Object matching that returned by the channel details and - // node details API endpoints, see backend for details of the - // object structure and keys. get_details method on ContentNode - // model as a starting point.` details: { type: Object, required: true, @@ -446,6 +522,13 @@ }, }, computed: { + ...mapGetters(['loggedIn']), + channelHref() { + if (this.loggedIn && !window.libraryMode) { + return window.Urls.channel(this._details.id); + } + return null; + }, _details() { const details = cloneDeep(this.details); defaultsDeep(details, DEFAULT_DETAILS); @@ -552,6 +635,11 @@ channelUrl(channel) { return window.Urls.channel(channel.id); }, + viewInKolibri() { + if (this._details.demo_server_url) { + window.open(this._details.demo_server_url, '_blank'); + } + }, }, $trs: { /* eslint-disable kolibri/vue-no-unused-translations */ @@ -590,6 +678,13 @@ currentVersionHeading: 'Published version', primaryLanguageHeading: 'Primary language', unpublishedText: 'Unpublished', + viewInKolibri: 'View in Kolibri', + goToChannel: 'Go to Channel', + goToWebsite: 'Go to source website', + options: 'Options', + downloadButton: 'Download channel summary', + downloadPDF: 'Download PDF', + downloadCSV: 'Download CSV', }, }; @@ -606,6 +701,14 @@ } } + .details-layout { + align-items: center; + } + + .details-content { + padding-right: 16px; + } + .v-toolbar__title { font-weight: bold; }
- {{ _details.description }} -
+ {{ _details.description }} +