From b9c7f0262ca9094012cf60cf5ae7a77e8b75ab52 Mon Sep 17 00:00:00 2001 From: Jonas Heinrich <onny@project-insanity.org> Date: Sat, 19 Dec 2020 13:56:23 +0100 Subject: [PATCH] modularize show and episode elements --- src/components/ContentCollapsable.vue | 84 +++++++++ src/components/MediaHeader.vue | 212 +++++++++++++++++++++++ src/views/Episode.vue | 236 ++++++-------------------- src/views/Show.vue | 151 +--------------- 4 files changed, 353 insertions(+), 330 deletions(-) create mode 100644 src/components/ContentCollapsable.vue create mode 100644 src/components/MediaHeader.vue diff --git a/src/components/ContentCollapsable.vue b/src/components/ContentCollapsable.vue new file mode 100644 index 0000000..8c45a45 --- /dev/null +++ b/src/components/ContentCollapsable.vue @@ -0,0 +1,84 @@ +<!-- + - @copyright Copyright (c) 2020 Jonas Heinrich + - + - @author Jonas Heinrich <onny@project-insanity.org> + - + - @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="episodeContent"> + <div class="episodeContentHeader"> + <h2>{{ title }}</h2> + <div + v-show="showContent" + class="menuToggle" + @click="showContent = !showContent"> + {{ t('podcast', 'Hide') }} + </div> + <div + v-show="!showContent" + class="menuToggle" + @click="showContent = !showContent"> + {{ t('podcast', 'Show') }} + </div> + </div> + <div v-show="showContent"> + <slot /> + </div> + </div> +</template> + +<script> +export default { + name: 'ContentCollapsable', + props: { + title: { + type: String, + default: '', + }, + }, + data: () => ({ + showContent: true, + }), +} +</script> + +<style lang="scss"> +.episodeContent { + + .episodeContentHeader { + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + padding: 10px 30px; + z-index: 60; + position: sticky; + top: 50px; + background: white; + display: flex; + align-items: center; + margin-bottom: -1px; + } + h2 { + flex-grow: 1; + margin-bottom: 0px; + } + .menuToggle { + color: #1976d2; + cursor: pointer; + } +} +</style> diff --git a/src/components/MediaHeader.vue b/src/components/MediaHeader.vue new file mode 100644 index 0000000..cd2795a --- /dev/null +++ b/src/components/MediaHeader.vue @@ -0,0 +1,212 @@ +<!-- + - @copyright Copyright (c) 2020 Jonas Heinrich + - + - @author Jonas Heinrich <onny@project-insanity.org> + - + - @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="podcastHeaderBg" + :style="{ backgroundImage: `url(${imgurl})` }"> + <div class="podcastHeader"> + <div + class="podcastImage" + :style="{ backgroundImage: `url(${imgurl})` }" /> + <div class="podcastDescription"> + <h1>{{ title }}</h1> + <div class="podcastAuthor"> + by <a :href="htmlURL" target="_blank">{{ author }}</a> + </div> + <div class="podcastControls"> + <button class="icon-add-white podcastButton button primary new-button"> + {{ t('podcast', 'Subscribe') }} + </button> + <ul class="podcastCategory"> + <a v-for="(category, idx) in categories" + :key="idx" + :href="`#/browse/category/${categories[idx]}`"> + <li> + {{ getCategoryName(categories[idx]) }} + </li> + </a> + </ul> + </div> + <vue-show-more-text + :text="description" + additional-container-css="padding: 0px;" /> + <slot /> + </div> + </div> + </div> +</template> + +<script> +import vueShowMoreText from 'vue-show-more-text' +import { getCategoryName } from '../services/podcastApi' + +export default { + name: 'MediaHeader', + components: { + vueShowMoreText, + }, + props: { + imgurl: { + type: String, + default: '', + }, + title: { + type: String, + default: '', + }, + author: { + type: String, + default: '', + }, + htmlURL: { + type: String, + default: '', + }, + categories: { + type: Array, + default() { + return [] + }, + }, + description: { + type: String, + default: '', + }, + }, + methods: { + getCategoryName(categoryid) { + return getCategoryName(categoryid) + }, + }, +} +</script> + +<style lang="scss"> + +.podcastHeaderBg { + background-size: cover; + background-position: center center; + background-attachment: fixed; +} + +.podcastHeader { + width: 100%; + min-height: 300px; + display: flex; + color: white; + justify-content: center; + padding: 40px 20px; + /* gap: 30px; */ + background-color: rgba(0, 0, 0, .7); + backdrop-filter: blur(8px); + text-shadow: 1px 1px 2px rgba(0,0,0,.5); + + .button { + text-shadow: 0px 0px 0px; + } + + .podcastImage { + width: 200px; + height: 200px; + flex-shrink: 0; + background-size: cover; + background-position: center; + box-shadow: 0 4px 60px rgba(0,0,0,.5); + margin-right: 25px; + border-radius: 5px; + } + + .podcastDescription { + max-width: 500px; + width: 100%; + color: #ddd; + + h1 { + font-size: 30px; + font-weight: bold; + line-height: 1.2em; + color: white; + } + + .podcastAuthor { + padding-bottom: 5px; + + a { + color: white; + } + } + + .podcastControls { + display: flex; + align-items: center; + margin-bottom: 5px; + + .button { + margin-right: 10px; + } + } + + ul.podcastCategory li { + display: inline; + border: 1px solid var(--color-text-maxcontrast); + border-radius: var(--border-radius); + margin-right: 5px; + cursor: pointer; + color: var(--color-text-maxcontrast); + padding: 3px 6px; + } + + ul.podcastCategory li:hover { + border: 1px solid white; + color: white; + } + } + +} + +.podcastButton { + padding-left: 35px; + padding-right: 15px; + background-position: 10px center; +} + +@media only screen and (max-width: 500px) { + .podcastHeader { + flex-direction: column; + align-items: center; + + .podcastImage { + margin-right: 0px; + margin-bottom: 25px; + } + + .podcastDescription { + text-align: center; + + .podcastControls { + justify-content: center; + } + } + } +} + +</style> diff --git a/src/views/Episode.vue b/src/views/Episode.vue index 7f490da..e9abbc3 100644 --- a/src/views/Episode.vue +++ b/src/views/Episode.vue @@ -24,69 +24,49 @@ <ShowEmpty v-show="pageLoading" /> <transition name="fade"> <div v-show="!pageLoading"> - <div - class="podcastHeaderBg" - :style="{ backgroundImage: `url(${episode.imgURL})` }"> - <div class="podcastHeader"> - <div - class="podcastImage" - :style="{ backgroundImage: `url(${episode.imgURL})` }" /> - <div class="podcastDescription"> - <h1>{{ episode.title }}</h1> - <div class="podcastAuthor"> - {{ t('podcast', 'by') }} <a :href="episode.htmlURL" target="_blank">{{ episode.author }}</a> - </div> - <button class="icon-play-white podcastButton button primary new-button"> - {{ t('podcast', 'Play episode') }} - </button> - <br><br> - <span> - <b>Duration:</b> - {{ episode.duration_string }} - </span> - <br> - <span> - <b>Publication date:</b> - {{ readableDate(episode.pubdate) }} - </span> - </div> - </div> - </div> - <div class="episodeContent"> - <h2>{{ t('podcast', 'Episode notes') }}</h2> - <div - v-show="showNotes" - class="menuToggle" - @click="showNotes = !showNotes"> - {{ t('podcast', 'Hide notes') }} - </div> - <div - v-show="!showNotes" - class="menuToggle" - @click="showNotes = !showNotes"> - {{ t('podcast', 'Show notes') }} - </div> + <MediaHeader + :imgurl="episode.imgURL" + :title="episode.title" + :author="episode.author" + :html-url="episode.htmlURL"> + <button class="icon-play-white podcastButton button primary new-button"> + {{ t('podcast', 'Play episode') }} + </button> + <br><br> + <span> + <b>Duration:</b> + {{ episode.duration_string }} + </span> + <br> + <span> + <b>Publication date:</b> + {{ readableDate(episode.pubdate) }} + </span> + </MediaHeader> + <ContentCollapsable + title="Episode notes"> <!-- eslint-disable --> - <p - v-show="showNotes" - v-html="episode.description" /> - <!-- eslint-enable --> - </div> - <div v-show="episode.chapters" class="episodeContent"> - <h2>{{ t('podcast', 'Episode chapters') }}</h2> + <p v-html="episode.description" /> + <!-- eslint-enable --> + </ContentCollapsable> + <ContentCollapsable + v-show="episode.chapters" + title="Episode chapters"> <table class="chapterTable"> <tbody> <tr v-for="(chapter, idx) in episode.chapters" :key="idx"> <td class="timeColumn"> - {{ chapter.start }} + {{ readableTime(chapter.start) }} + </td> + <td class="titleColumn"> + {{ chapter.title }} </td> - <td>{{ chapter.title }}</td> </tr> </tbody> </table> - </div> + </ContentCollapsable> </div> </transition> </div> @@ -97,6 +77,8 @@ import { showError } from '@nextcloud/dialogs' import axios from '@nextcloud/axios' import ShowEmpty from './ShowEmpty' +import ContentCollapsable from '../components/ContentCollapsable' +import MediaHeader from '../components/MediaHeader' import TimeAgo from 'javascript-time-ago' @@ -106,11 +88,12 @@ export default { name: 'Episode', components: { ShowEmpty, + ContentCollapsable, + MediaHeader, }, data: () => ({ episode: {}, pageLoading: false, - showNotes: true, }), mounted() { this.pageLoading = true @@ -119,6 +102,9 @@ export default { }, methods: { + readableTime(timestamp) { + return timestamp.split('.')[0] + }, readableDate(datetime) { return timeAgo.format(Date.parse(datetime), 'twitter-minute-now') }, @@ -154,137 +140,11 @@ export default { <style lang="scss"> -.podcastHeaderBg { - background-size: cover; - background-position: center center; - background-attachment: fixed; -} - -.podcastHeader { - width: 100%; - min-height: 300px; - display: flex; - color: white; - justify-content: center; - padding: 40px 20px; - /* gap: 30px; */ - background-color: rgba(0, 0, 0, .7); - backdrop-filter: blur(8px); - text-shadow: 1px 1px 2px rgba(0,0,0,.5); - - .button { - text-shadow: 0px 0px 0px; - } - - .podcastImage { - width: 200px; - height: 200px; - flex-shrink: 0; - background-size: cover; - background-position: center; - box-shadow: 0 4px 60px rgba(0,0,0,.5); - margin-right: 25px; - border-radius: 5px; - } - - .podcastDescription { - max-width: 500px; - width: 100%; - color: #ddd; - - h1 { - font-size: 30px; - font-weight: bold; - line-height: 1.2em; - color: white; - } - - .podcastAuthor { - padding-bottom: 5px; - - a { - color: white; - } - } - - .podcastControls { - display: flex; - align-items: center; - margin-bottom: 5px; - - .button { - margin-right: 10px; - } - } - - ul.podcastCategory li { - display: inline; - border: 1px solid var(--color-text-maxcontrast); - border-radius: var(--border-radius); - margin-right: 5px; - cursor: pointer; - color: var(--color-text-maxcontrast); - padding: 3px 6px; - } - - ul.podcastCategory li:hover { - border: 1px solid white; - color: white; - } - } - -} - -.empty-content { - margin-top: 10px !important; - - .empty-content__icon { - margin-bottom: 0px !important; - } -} - -@media only screen and (max-width: 500px) { - .podcastHeader { - flex-direction: column; - align-items: center; - - .podcastImage { - margin-right: 0px; - margin-bottom: 25px; - } - - .podcastDescription { - text-align: center; - - .podcastControls { - justify-content: center; - } - } - } -} - .episodeContent { - h2 { - border-bottom: 1px solid #ddd; - border-top: 1px solid #ddd; - padding: 10px 30px; - z-index: 60; - position: sticky; - top: 50px; - background: white; - } p { padding: 10px 30px; - max-width: 700px; - } - .menuToggle { - float: right; - color: #1976d2; - cursor: pointer; - position: relative; - right: 20px; - top: -49px; - z-index: 60; + max-width: 550px; + margin-top: 1px; } } @@ -293,11 +153,13 @@ table.chapterTable { min-width: 250px; table-layout:fixed; position: relative; + margin-top: 1px; tbody { td { - padding: 10px 30px; + padding: 15px 0px; + padding-left: 30px; font-style: normal; border-bottom: 1px solid var(--color-border); cursor: pointer; @@ -323,13 +185,11 @@ table.chapterTable { color: #1976d2; } - } -} + td.titleColumn { + padding-left: 0px; + } -.podcastButton { - padding-left: 35px; - padding-right: 15px; - background-position: 10px center; + } } </style> diff --git a/src/views/Show.vue b/src/views/Show.vue index 63a6540..3e365a7 100644 --- a/src/views/Show.vue +++ b/src/views/Show.vue @@ -24,38 +24,13 @@ <ShowEmpty v-show="pageLoading" /> <transition name="fade"> <div v-show="!pageLoading"> - <div - class="podcastHeaderBg" - :style="{ backgroundImage: `url(${podcast.smallImageURL})` }"> - <div class="podcastHeader"> - <div - class="podcastImage" - :style="{ backgroundImage: `url(${podcast.smallImageURL})` }" /> - <div class="podcastDescription"> - <h1>{{ podcast.title }}</h1> - <div class="podcastAuthor"> - by <a :href="podcast.htmlURL" target="_blank">{{ podcast.author }}</a> - </div> - <div class="podcastControls"> - <button class="icon-add-white podcastButton button primary new-button"> - {{ t('podcast', 'Subscribe') }} - </button> - <ul class="podcastCategory"> - <a v-for="(category, idx) in podcast.categories" - :key="idx" - :href="`#/browse/category/${podcast.categories[idx]}`"> - <li> - {{ getCategoryName(podcast.categories[idx]) }} - </li> - </a> - </ul> - </div> - <vue-show-more-text - :text="podcast.description" - additional-container-css="padding: 0px;" /> - </div> - </div> - </div> + <MediaHeader + :imgurl="podcast.smallImageURL" + :title="podcast.title" + :author="podcast.author" + :html-url="podcast.htmlURL" + :categories="podcast.categories" + :description="podcast.description" /> <Table v-resize="onResize" :episodes="podcast.episodes" @@ -70,15 +45,13 @@ <script> import Table from '../components/Table' +import MediaHeader from '../components/MediaHeader' import { showError } from '@nextcloud/dialogs' import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' import axios from '@nextcloud/axios' -import vueShowMoreText from 'vue-show-more-text' - import { audioPlayer, doPlay } from '../services/player' -import { getCategoryName } from '../services/podcastApi' import ShowEmpty from './ShowEmpty' @@ -87,7 +60,7 @@ export default { components: { EmptyContent, Table, - vueShowMoreText, + MediaHeader, ShowEmpty, }, data: () => ({ @@ -125,10 +98,6 @@ export default { }, methods: { - getCategoryName(categoryid) { - return getCategoryName(categoryid) - }, - onResize({ width, height }) { const contentHeight = document.getElementById('app-content-vue').scrollHeight const tableHeight = height @@ -144,7 +113,6 @@ export default { preFill() { const route = this.$route - console.log('lol') console.log(route) // this.queryPodcast(route.name) }, @@ -205,87 +173,6 @@ export default { <style lang="scss"> -.podcastHeaderBg { - background-size: cover; - background-position: center center; - background-attachment: fixed; -} - -.podcastHeader { - width: 100%; - min-height: 300px; - display: flex; - color: white; - justify-content: center; - padding: 40px 20px; - /* gap: 30px; */ - background-color: rgba(0, 0, 0, .7); - backdrop-filter: blur(8px); - text-shadow: 1px 1px 2px rgba(0,0,0,.5); - - .button { - text-shadow: 0px 0px 0px; - } - - .podcastImage { - width: 200px; - height: 200px; - flex-shrink: 0; - background-size: cover; - background-position: center; - box-shadow: 0 4px 60px rgba(0,0,0,.5); - margin-right: 25px; - border-radius: 5px; - } - - .podcastDescription { - max-width: 500px; - width: 100%; - color: #ddd; - - h1 { - font-size: 30px; - font-weight: bold; - line-height: 1.2em; - color: white; - } - - .podcastAuthor { - padding-bottom: 5px; - - a { - color: white; - } - } - - .podcastControls { - display: flex; - align-items: center; - margin-bottom: 5px; - - .button { - margin-right: 10px; - } - } - - ul.podcastCategory li { - display: inline; - border: 1px solid var(--color-text-maxcontrast); - border-radius: var(--border-radius); - margin-right: 5px; - cursor: pointer; - color: var(--color-text-maxcontrast); - padding: 3px 6px; - } - - ul.podcastCategory li:hover { - border: 1px solid white; - color: white; - } - } - -} - .empty-content { margin-top: 10px !important; @@ -294,24 +181,4 @@ export default { } } -@media only screen and (max-width: 500px) { - .podcastHeader { - flex-direction: column; - align-items: center; - - .podcastImage { - margin-right: 0px; - margin-bottom: 25px; - } - - .podcastDescription { - text-align: center; - - .podcastControls { - justify-content: center; - } - } - } -} - </style> -- GitLab