From 3a4d3e941c5dd147b30c48ce140208e38ca7bf37 Mon Sep 17 00:00:00 2001 From: Jonas Heinrich <onny@project-insanity.org> Date: Sun, 13 Dec 2020 18:13:59 +0100 Subject: [PATCH] add category name, fix linking author, categories and episodes --- package-lock.json | 16 + package.json | 1 + src/App.vue | 45 ++- src/assets/categories.json | 707 ++++++++++++++++++++++++++++++++++ src/components/Navigation.vue | 2 +- src/components/Table.vue | 82 +++- src/main.js | 4 +- src/services/player.js | 39 ++ src/services/podcastApi.js | 20 + src/views/Episode.vue | 49 --- src/views/Show.vue | 244 +++++------- 11 files changed, 997 insertions(+), 212 deletions(-) create mode 100644 src/assets/categories.json create mode 100644 src/services/player.js create mode 100644 src/services/podcastApi.js diff --git a/package-lock.json b/package-lock.json index 5e1d672..6aa4a66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11403,6 +11403,22 @@ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz", "integrity": "sha512-CGAKWN44RqXW06oC+u4mPgHLQQi2t6vLD/JbGRDAXm0YpMv0bgpKuU5bBd7AvMgfTz9kXVRIWKHqRwGEb8xFkA==" }, + "vue-show-more-text": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vue-show-more-text/-/vue-show-more-text-2.0.2.tgz", + "integrity": "sha512-x/WuikWAx8Hm4gpZx6KHtJYiXDordGdSoXrd34lTiJeAnlT8Y7Yc0FfGBNdUv6mXncuET3LiRwwNz+X5gI+oiw==", + "requires": { + "core-js": "^3.6.5", + "vue": "^2.6.11" + }, + "dependencies": { + "core-js": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.1.tgz", + "integrity": "sha512-9Id2xHY1W7m8hCl8NkhQn5CufmF/WuR30BTRewvCXc1aZd3kMECwNZ69ndLbekKfakw9Rf2Xyc+QR6E7Gg+obg==" + } + } + }, "vue-style-loader": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz", diff --git a/package.json b/package.json index e7c6b83..d173c44 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "vue-clipboard2": "^0.3.1", "vue-resize-observer": "^1.0.32", "vue-router": "^3.4.9", + "vue-show-more-text": "^2.0.2", "vuex": "^3.5.1" }, "browserslist": [ diff --git a/src/App.vue b/src/App.vue index 553a857..6df74e8 100644 --- a/src/App.vue +++ b/src/App.vue @@ -21,5 +21,48 @@ --> <template> - <router-view /> + <Content app-name="podcast"> + <Navigation /> + <AppContent> + <router-view /> + </AppContent> + </Content> </template> + +<script> +import Content from '@nextcloud/vue/dist/Components/Content' +import AppContent from '@nextcloud/vue/dist/Components/AppContent' +import Navigation from './components/Navigation' + +export default { + name: 'App', + components: { + Content, + Navigation, + AppContent, + }, + created() { + this.loadSettings() + }, + methods: { + loadSettings() { + + // axios.defaults.headers.common = { + // 'User-Agent': 'Nextcloud Podcast App/' + this.$version, + // } + this.$store.dispatch('getVolumeState') + + }, + }, +} +</script> + +<style lang="scss"> + +@media only screen and (min-width: 1024px) { + .app-navigation-toggle { + display: none; + } +} + +</style> diff --git a/src/assets/categories.json b/src/assets/categories.json new file mode 100644 index 0000000..e04a054 --- /dev/null +++ b/src/assets/categories.json @@ -0,0 +1,707 @@ +{ + "status": 1, + "msg": "ok", + "meta": { + "API_INFO": { + "API_VERSION": "0.2" + }, + "SERVER": "2a01:4f8:1c0c:7bfd::1", + "duration": 0 + }, + "data": [ + { + "id": 100, + "slug": "arts", + "name": "Arts", + "name_de": "Kunst", + "subcategories": [ + { + "id": 102, + "slug": "books", + "name": "Books", + "name_de": "B\u00fccher" + }, + { + "id": 103, + "slug": "design", + "name": "Design", + "name_de": "Design" + }, + { + "id": 104, + "slug": "fashion-beauty", + "name": "Fashion & Beauty", + "name_de": "Mode & Sch\u00f6nheit" + }, + { + "id": 105, + "slug": "food", + "name": "Food", + "name_de": "Lebensmittel" + }, + { + "id": 106, + "slug": "performing-arts", + "name": "Performing Arts", + "name_de": "Darstellende Kunst" + }, + { + "id": 107, + "slug": "visual-arts", + "name": "Visual Arts", + "name_de": "Bildende Kunst" + } + ] + }, + { + "id": 108, + "slug": "business", + "name": "Business", + "name_de": "Wirtschaft", + "subcategories": [ + { + "id": 109, + "slug": "careers", + "name": "Careers", + "name_de": "Karriere" + }, + { + "id": 110, + "slug": "entrepreneurship", + "name": "Entrepreneurship", + "name_de": "Unternehmertum" + }, + { + "id": 111, + "slug": "investing", + "name": "Investing", + "name_de": "Investitionen" + }, + { + "id": 112, + "slug": "management", + "name": "Management", + "name_de": "Unternehmensf\u00fchrung" + }, + { + "id": 113, + "slug": "marketing", + "name": "Marketing", + "name_de": "Marketing" + }, + { + "id": 114, + "slug": "non-profit", + "name": "Non-profit", + "name_de": "Gemeinn\u00fctzig" + } + ] + }, + { + "id": 115, + "slug": "comedy", + "name": "Comedy", + "name_de": "Comedy", + "subcategories": [ + { + "id": 116, + "slug": "comedy-interviews", + "name": "Comedy Interviews", + "name_de": "Interviews" + }, + { + "id": 117, + "slug": "improv", + "name": "Improv", + "name_de": "Improvisation" + }, + { + "id": 118, + "slug": "standup", + "name": "Standup", + "name_de": "Stand-Up" + } + ] + }, + { + "id": 119, + "slug": "education", + "name": "Education", + "name_de": "Bildung", + "subcategories": [ + { + "id": 120, + "slug": "courses", + "name": "Courses", + "name_de": "Sprachkurse" + }, + { + "id": 121, + "slug": "how-to", + "name": "How to", + "name_de": "Anleitung" + }, + { + "id": 122, + "slug": "language-learning", + "name": "Language Learning", + "name_de": "Sprache lernen" + }, + { + "id": 123, + "slug": "self-improvement", + "name": "Self Improvement", + "name_de": "Selbstentwicklung" + } + ] + }, + { + "id": 124, + "slug": "fiction", + "name": "Fiction", + "name_de": "Fiktion", + "subcategories": [ + { + "id": 125, + "slug": "comedy-fiction", + "name": "Comedy Fiction", + "name_de": "Fiktion-Comedy" + }, + { + "id": 126, + "slug": "drama", + "name": "Drama", + "name_de": "Drama" + }, + { + "id": 127, + "slug": "science-fiction", + "name": "Science Fiction", + "name_de": "Science-Fiction" + } + ] + }, + { + "id": 128, + "slug": "government", + "name": "Government", + "name_de": "Regierung", + "subcategories": [] + }, + { + "id": 129, + "slug": "history", + "name": "History", + "name_de": "Geschichte", + "subcategories": [] + }, + { + "id": 130, + "slug": "health-fitness", + "name": "Health & Fitness", + "name_de": "Gesundheit & Fitness", + "subcategories": [ + { + "id": 131, + "slug": "alternative-health", + "name": "Alternative Health", + "name_de": "Alternative Gesundheit" + }, + { + "id": 132, + "slug": "fitness", + "name": "Fitness", + "name_de": "Fitness" + }, + { + "id": 133, + "slug": "medicine", + "name": "Medicine", + "name_de": "Medizin" + }, + { + "id": 134, + "slug": "mental-health", + "name": "Mental Health", + "name_de": "Psychische Gesundheit" + }, + { + "id": 135, + "slug": "nutrition", + "name": "Nutrition", + "name_de": "Ern\u00e4hrung" + }, + { + "id": 136, + "slug": "sexuality", + "name": "Sexuality", + "name_de": "Sexualit\u00e4t" + } + ] + }, + { + "id": 137, + "slug": "kids-family", + "name": "Kids & Family", + "name_de": "Kinder & Familie", + "subcategories": [ + { + "id": 138, + "slug": "education-for-kids", + "name": "Education for Kids", + "name_de": "Bildung f\u00fcr Kinder" + }, + { + "id": 139, + "slug": "parenting", + "name": "Parenting", + "name_de": "Erziehung" + }, + { + "id": 140, + "slug": "pets-animals", + "name": "Pets & Animals", + "name_de": "Haustiere" + }, + { + "id": 141, + "slug": "stories-for-kids", + "name": "Stories for Kids", + "name_de": "Geschichten f\u00fcr Kinder" + } + ] + }, + { + "id": 142, + "slug": "leisure", + "name": "Leisure", + "name_de": "Freizeit", + "subcategories": [ + { + "id": 143, + "slug": "animation-manga", + "name": "Animation & Manga", + "name_de": "Animation & Manga" + }, + { + "id": 144, + "slug": "automotive", + "name": "Automotive", + "name_de": "Auto" + }, + { + "id": 145, + "slug": "aviation", + "name": "Aviation", + "name_de": "Luftfahrt" + }, + { + "id": 146, + "slug": "crafts", + "name": "Crafts", + "name_de": "Handwerk" + }, + { + "id": 147, + "slug": "games", + "name": "Games", + "name_de": "Spiele" + }, + { + "id": 148, + "slug": "hobbies", + "name": "Hobbies", + "name_de": "Hobbies" + }, + { + "id": 149, + "slug": "home-garden", + "name": "Home & Garden", + "name_de": "Haus & Garten" + }, + { + "id": 150, + "slug": "video-games", + "name": "Video Games", + "name_de": "Videospiele" + } + ] + }, + { + "id": 151, + "slug": "music", + "name": "Music", + "name_de": "Musik", + "subcategories": [ + { + "id": 152, + "slug": "music-commentary", + "name": "Music Commentary", + "name_de": "Musik-Kommentare" + }, + { + "id": 153, + "slug": "music-history", + "name": "Music History", + "name_de": "Musik-Geschichte" + }, + { + "id": 154, + "slug": "music-interviews", + "name": "Music Interviews", + "name_de": "Musik-Interviews" + } + ] + }, + { + "id": 155, + "slug": "news", + "name": "News", + "name_de": "Nachrichten", + "subcategories": [ + { + "id": 156, + "slug": "business-news", + "name": "Business News", + "name_de": "Gesch\u00e4ftsnachrichten" + }, + { + "id": 157, + "slug": "daily-news", + "name": "Daily News", + "name_de": "T\u00e4gliche Nachrichten" + }, + { + "id": 158, + "slug": "entertainment-news", + "name": "Entertainment News", + "name_de": "Unterhaltungsnachrichten" + }, + { + "id": 159, + "slug": "news-commentary", + "name": "News Commentary", + "name_de": "Nachrichtenkommentare" + }, + { + "id": 160, + "slug": "politics", + "name": "Politics", + "name_de": "Politik" + }, + { + "id": 161, + "slug": "sports-news", + "name": "Sports News", + "name_de": "Sportnachrichten" + }, + { + "id": 162, + "slug": "tech-news", + "name": "Tech News", + "name_de": "Techniknachrichten" + } + ] + }, + { + "id": 163, + "slug": "religion-spirituality", + "name": "Religion & Spirituality", + "name_de": "Religion & Spiritualit\u00e4t", + "subcategories": [ + { + "id": 164, + "slug": "buddhism", + "name": "Buddhism", + "name_de": "Buddhismus" + }, + { + "id": 165, + "slug": "christianity", + "name": "Christianity", + "name_de": "Christentum" + }, + { + "id": 166, + "slug": "hinduism", + "name": "Hinduism", + "name_de": "Hinduismus" + }, + { + "id": 167, + "slug": "islam", + "name": "Islam", + "name_de": "Islam" + }, + { + "id": 168, + "slug": "judaism", + "name": "Judaism", + "name_de": "Judentum" + }, + { + "id": 169, + "slug": "religion", + "name": "Religion", + "name_de": "Religion" + }, + { + "id": 170, + "slug": "spirituality", + "name": "Spirituality", + "name_de": "Spiritualit\u00e4t" + } + ] + }, + { + "id": 171, + "slug": "science", + "name": "Science", + "name_de": "Wissenschaft", + "subcategories": [ + { + "id": 172, + "slug": "astronomy", + "name": "Astronomy", + "name_de": "Astronomie" + }, + { + "id": 173, + "slug": "chemistry", + "name": "Chemistry", + "name_de": "Chemie" + }, + { + "id": 174, + "slug": "earth-sciences", + "name": "Earth Sciences", + "name_de": "Geologie" + }, + { + "id": 175, + "slug": "life-sciences", + "name": "Life Sciences", + "name_de": "Biologie" + }, + { + "id": 176, + "slug": "mathematics", + "name": "Mathematics", + "name_de": "Mathematik" + }, + { + "id": 177, + "slug": "natural-sciences", + "name": "Natural Sciences", + "name_de": "Naturwissenschaft" + }, + { + "id": 178, + "slug": "nature", + "name": "Nature", + "name_de": "Natur" + }, + { + "id": 179, + "slug": "physics", + "name": "Physics", + "name_de": "Physik" + }, + { + "id": 180, + "slug": "social-sciences", + "name": "Social Sciences", + "name_de": "Sozialwissenschaften" + } + ] + }, + { + "id": 181, + "slug": "society-culture", + "name": "Society & Culture", + "name_de": "Gesellschaft & Kultur", + "subcategories": [ + { + "id": 182, + "slug": "documentary", + "name": "Documentary", + "name_de": "Dokumentation" + }, + { + "id": 183, + "slug": "personal-journals", + "name": "Personal Journals", + "name_de": "Tageb\u00fccher" + }, + { + "id": 184, + "slug": "philosophy", + "name": "Philosophy", + "name_de": "Philosophie" + }, + { + "id": 185, + "slug": "places-travel", + "name": "Places & Travel", + "name_de": "Orte & Reisen" + }, + { + "id": 186, + "slug": "relationships", + "name": "Relationships", + "name_de": "Beziehungen" + } + ] + }, + { + "id": 187, + "slug": "sports", + "name": "Sports", + "name_de": "Sport", + "subcategories": [ + { + "id": 188, + "slug": "baseball", + "name": "Baseball", + "name_de": "Baseball" + }, + { + "id": 189, + "slug": "basketball", + "name": "Basketball", + "name_de": "Basketball" + }, + { + "id": 190, + "slug": "cricket", + "name": "Cricket", + "name_de": "Kricket" + }, + { + "id": 191, + "slug": "fantasy-sports", + "name": "Fantasy Sports", + "name_de": "Fantasiesport" + }, + { + "id": 192, + "slug": "football", + "name": "Football", + "name_de": "Football" + }, + { + "id": 193, + "slug": "golf", + "name": "Golf", + "name_de": "Golf" + }, + { + "id": 194, + "slug": "hockey", + "name": "Hockey", + "name_de": "Hockey" + }, + { + "id": 195, + "slug": "rugby", + "name": "Rugby", + "name_de": "Rugby" + }, + { + "id": 196, + "slug": "running", + "name": "Running", + "name_de": "Laufen" + }, + { + "id": 197, + "slug": "soccer", + "name": "Soccer", + "name_de": "Fu\u00dfball" + }, + { + "id": 198, + "slug": "swimming", + "name": "Swimming", + "name_de": "Schwimmen" + }, + { + "id": 199, + "slug": "tennis", + "name": "Tennis", + "name_de": "Tennis" + }, + { + "id": 200, + "slug": "volleyball", + "name": "Volleyball", + "name_de": "Volleyball" + }, + { + "id": 201, + "slug": "wilderness", + "name": "Wilderness", + "name_de": "Wildnis" + }, + { + "id": 202, + "slug": "wrestling", + "name": "Wrestling", + "name_de": "Wrestling" + } + ] + }, + { + "id": 203, + "slug": "technology", + "name": "Technology", + "name_de": "Technik", + "subcategories": [] + }, + { + "id": 204, + "slug": "true-crime", + "name": "True Crime", + "name_de": "Wahre Kriminalf\u00e4lle", + "subcategories": [] + }, + { + "id": 205, + "slug": "tv-film", + "name": "TV & Film", + "name_de": "TV & Film", + "subcategories": [ + { + "id": 206, + "slug": "after-shows", + "name": "After Shows", + "name_de": "After Shows" + }, + { + "id": 207, + "slug": "film-history", + "name": "Film History", + "name_de": "Filmgeschichte" + }, + { + "id": 208, + "slug": "film-interviews", + "name": "Film Interviews", + "name_de": "Filminterviews" + }, + { + "id": 209, + "slug": "film-reviews", + "name": "Film Reviews", + "name_de": "Filmrezensionen" + }, + { + "id": 210, + "slug": "tv-reviews", + "name": "TV Reviews", + "name_de": "TV-Rezensionen" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/components/Navigation.vue b/src/components/Navigation.vue index 9a2b541..e29273f 100644 --- a/src/components/Navigation.vue +++ b/src/components/Navigation.vue @@ -29,7 +29,7 @@ :title="t('podcast', 'Listening')" /> <AppNavigationItem :to="{ name: 'LIBRARY' }" - icon="icon-files" + icon="icon-files-dark" :title="t('podcast', 'Library')" /> <AppNavigationItem :to="{ name: 'BROWSE' }" diff --git a/src/components/Table.vue b/src/components/Table.vue index f3f4d16..d4a4ddf 100644 --- a/src/components/Table.vue +++ b/src/components/Table.vue @@ -29,10 +29,10 @@ {{ t('podcast', 'Name') }} </th> <th class="actionColumn" /> - <th style="width: 90px"> + <th class="durationColumn" style="width: 90px"> {{ t('podcast', 'Duration') }} </th> - <th style="width: 130px"> + <th class="dateColumn" style="width: 130px"> {{ t('podcast', 'Date') }} </th> </tr> @@ -43,15 +43,21 @@ :key="idx" :class="{ selected: idx === activeItem}"> <td class="iconColumn"> - <blur-hash-image - width="64" - height="64" - hash="L1TSUA?bj[?b~qfQfQj[ayfQfQfQ" - :src="episode.imgURL" /> + <div style="width: 64px; height: 64px;"> + <blur-hash-image + hash="L1TSUA?bj[?b~qfQfQj[ayfQfQfQ" + :src="episode.imgURL" /> + </div> </td> - <td class="nameColumn"> + <td + class="nameColumn" + @click="changeRoute(`/#/show/${episode.podcast_id}/${episode.id}`)"> <b>{{ episode.title }}</b> - <span>{{ episode.description }}</span> + <vue-show-more-text + :text="escapedEpisodeDescription(episode.description)" + :lines="2" + :has-more="false" + additional-container-css="padding: 0px;" /> </td> <td class="actionColumn"> <Actions> @@ -64,7 +70,7 @@ <ActionButton icon="icon-info" :close-after-click="true" - @click="changeRoute('/#/show/1084/5280308')"> + @click="changeRoute(`/#/show/${episode.podcast_id}/${episode.id}`)"> {{ t('podcast', 'Show') }} </ActionButton> <ActionButton @@ -80,10 +86,14 @@ </ActionButton> </Actions> </td> - <td> + <td + class="durationColumn" + @click="changeRoute(`/#/show/${episode.podcast_id}/${episode.id}`)"> {{ episode.duration_string }} </td> - <td> + <td + class="dateColumn" + @click="changeRoute(`/#/show/${episode.podcast_id}/${episode.id}`)"> {{ readableTime(episode.inserted) }} </td> </tr> @@ -96,6 +106,7 @@ import Actions from '@nextcloud/vue/dist/Components/Actions' import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' import TimeAgo from 'javascript-time-ago' import en from 'javascript-time-ago/locale/en' +import vueShowMoreText from 'vue-show-more-text' TimeAgo.addDefaultLocale(en) const timeAgo = new TimeAgo('en-US') @@ -105,6 +116,7 @@ export default { components: { Actions, ActionButton, + vueShowMoreText, }, props: { episodes: { @@ -116,8 +128,12 @@ export default { activeItem: null, }), methods: { + escapedEpisodeDescription(episodeDescription) { + episodeDescription = episodeDescription.replace(/\n/g, '') + return episodeDescription + }, readableTime(datetime) { - return timeAgo.format(Date.parse(datetime)) + return timeAgo.format(Date.parse(datetime), 'twitter-minute-now') }, downloadFile(episodeURL) { window.open(episodeURL, 'download') @@ -127,6 +143,7 @@ export default { this.$emit('doPlay', episode) }, changeRoute(path) { + console.log(path) this.$router.push({ path }) }, }, @@ -159,9 +176,6 @@ table { border-bottom: 1px solid var(--color-border); padding: 15px; height: 50px; - } - - th, th a { color: var(--color-text-maxcontrast); } @@ -240,4 +254,40 @@ table { } +@media only screen and (max-width: 500px) { + table { + thead { + th.iconColumn { + width: 85px; + } + th.nameColumn { + padding-left: 0px; + } + th.actionColumn { + padding-left: 5px; + padding-right: 5px; + width: 50px; + } + th.durationColumn, th.dateColumn { + display: none; + } + } + tbody { + td.iconColumn { + padding-left: 10px; + } + td.nameColumn { + padding-left: 0px; + } + td.actionColumn { + padding-left: 5px; + padding-right: 5px; + width: 50px; + } + td.durationColumn, td.dateColumn { + display: none; + } + } + } +} </style> diff --git a/src/main.js b/src/main.js index 5c0b4cc..6de012b 100644 --- a/src/main.js +++ b/src/main.js @@ -38,8 +38,8 @@ Vue.prototype.t = translate Vue.prototype.n = translatePlural Vue.prototype.OC = window.OC Vue.prototype.OCA = window.OCA -Vue.prototype.$apiUrl = 'https://de1.api.radio-browser.info' -Vue.prototype.$version = '1.0.1' +Vue.prototype.$apiUrl = 'https://api.fyyd.de/0.2' +Vue.prototype.$version = '0.0.1' Vue.use(VueClipboard) Vue.use(VueBlurHash) diff --git a/src/services/player.js b/src/services/player.js new file mode 100644 index 0000000..2e405db --- /dev/null +++ b/src/services/player.js @@ -0,0 +1,39 @@ +import { Howl, Howler } from 'howler' +import { showError } from '@nextcloud/dialogs' + +export let audioPlayer +audioPlayer = null + +export function doPlay(episode) { + const vm = this + + vm.$store.dispatch('isBuffering', true) + + if (audioPlayer !== null) { + audioPlayer.fade(vm.player.volume, 0, 500) + } + vm.$store.dispatch('setTitle', episode.title) + + Howler.unload() + audioPlayer = new Howl({ + src: episode.enclosure, + html5: true, + volume: vm.player.volume, + onplay() { + vm.$store.dispatch('isPlaying', true) + vm.$store.dispatch('isBuffering', false) + }, + onpause() { + vm.$store.dispatch('isPlaying', false) + vm.$store.dispatch('isBuffering', false) + }, + onend() { + showError(t('podcast', 'Lost connection to podcast station, retrying ...')) + vm.$store.dispatch('isPlaying', false) + vm.$store.dispatch('isBuffering', true) + }, + }) + audioPlayer.unload() + audioPlayer.play() + audioPlayer.fade(0, vm.player.volume, 500) +} diff --git a/src/services/podcastApi.js b/src/services/podcastApi.js new file mode 100644 index 0000000..ac0a5c6 --- /dev/null +++ b/src/services/podcastApi.js @@ -0,0 +1,20 @@ +const categories = require('../assets/categories.json') + +export function getCategoryName(categoryid) { + for (let i = 0; i < categories.data.length; i++) { + const obj = categories.data[i] + if (obj.id === categoryid) { + return obj.name + } + if ('subcategories' in categories.data[i]) { + const subcategories = categories.data[i].subcategories + for (let i = 0; i < subcategories.length; i++) { + const obj = subcategories[i] + if (obj.id === categoryid) { + return obj.name + } + } + } + } + return categoryid +} diff --git a/src/views/Episode.vue b/src/views/Episode.vue index 52427c8..6cfd6ea 100644 --- a/src/views/Episode.vue +++ b/src/views/Episode.vue @@ -402,53 +402,4 @@ export default { } } -.podcastHeader { - width: 100%; - background: black; - min-height: 300px; - display: flex; - color: white; - justify-content: center; - padding: 40px 20px; - gap: 30px; - - .podcastImage { - width: 230px; - height: 230px; - background: red; - background-size: cover; - background-position: center center; - } - - .podcastDescription { - max-width: 500px; - max-height: 200px; - overflow: hidden; - text-overflow: ellipsis; - color: #ddd; - - h1 { - font-size: 30px; - font-weight: bold; - line-height: 1.2em; - color: white; - } - - .podcastAuthor { - padding-bottom: 10px; - - a { - color: white; - } - } - - ul.podcastCategory li { - padding: 5px; - background: red; - } - - } - -} - </style> diff --git a/src/views/Show.vue b/src/views/Show.vue index 88fa555..0216629 100644 --- a/src/views/Show.vue +++ b/src/views/Show.vue @@ -21,10 +21,10 @@ --> <template> - <Content app-name="podcast"> - <Navigation - :station-data="tableData" /> - <AppContent> + <div> + <div + class="podcastHeaderBg" + :style="{ backgroundImage: `url(${podcast.smallImageURL})` }"> <div class="podcastHeader"> <div class="podcastImage" @@ -32,70 +32,60 @@ <div class="podcastDescription"> <h1>{{ podcast.title }}</h1> <div class="podcastAuthor"> - by <a href="#">{{ podcast.author }}</a> + by <a :href="`#/browse/author/${podcast.author}`">{{ podcast.author }}</a> </div> - <div style="height: 40px; overflow: hidden; padding-bottom: 5px;"> - {{ podcast.description }} + <div class="podcastControls"> + <a href="#" class="button">Subscribe</a> + <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> - <ul class="podcastCategory"> - <li - v-for="(category, idx) in podcast.categories" - :key="idx"> - {{ podcast.categories[idx] }} - </li> - </ul> - <Actions default-icon="icon-add-white" :primary="true" menu-title="Subscribe" /> + <vue-show-more-text + :text="podcast.description" + additional-container-css="padding: 0px;" /> </div> </div> - <Table - v-show="!pageLoading && podcast.episodes.length > 0" - v-resize="onResize" - :episodes="podcast.episodes" - @doPlay="doPlay" /> - <EmptyContent - v-if="pageLoading" - icon="icon-loading" /> - <EmptyContent - v-if="tableData.length === 0 && !pageLoading" - :icon="emptyContentIcon"> - {{ emptyContentMessage }} - <template #desc> - {{ emptyContentDesc }} - </template> - </EmptyContent> - </AppContent> - </Content> + </div> + <Table + v-show="!pageLoading && podcast.episodes.length > 0" + v-resize="onResize" + :episodes="podcast.episodes" + @doPlay="doPlay" /> + <EmptyContent + v-if="pageLoading" + icon="icon-loading" /> + </div> </template> <script> -import Content from '@nextcloud/vue/dist/Components/Content' -import AppContent from '@nextcloud/vue/dist/Components/AppContent' import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' -import Actions from '@nextcloud/vue/dist/Components/Actions' -import Navigation from '../components/Navigation' import Table from '../components/Table' -import { Howl, Howler } from 'howler' import { showError } from '@nextcloud/dialogs' import axios from '@nextcloud/axios' -let audioPlayer = null +import vueShowMoreText from 'vue-show-more-text' + +import { audioPlayer, doPlay } from '../services/player' +import { getCategoryName } from '../services/podcastApi' export default { name: 'Show', components: { - Navigation, - Content, - AppContent, Table, EmptyContent, - Actions, + vueShowMoreText, }, data: () => ({ podcast: { episodes: [], }, - tableData: [], pageLoading: false, queryParams: {}, }), @@ -103,36 +93,6 @@ export default { player() { return this.$store.state.player }, - emptyContentMessage() { - if (this.$route.name === 'FAVORITES') { - return t('podcast', 'No favorites yet') - } else if (this.$route.name === 'RECENT') { - return t('podcast', 'No recent stations yet') - } else if (this.$route.name === 'SEARCH') { - return t('podcast', 'No search results') - } - return 'No stations here' - }, - emptyContentIcon() { - if (this.$route.name === 'FAVORITES') { - return 'icon-star' - } else if (this.$route.name === 'RECENT') { - return 'icon-recent' - } else if (this.$route.name === 'SEARCH') { - return 'icon-search' - } - return 'icon-podcast' - }, - emptyContentDesc() { - if (this.$route.name === 'FAVORITES') { - return t('podcast', 'Stations you mark as favorite will show up here') - } else if (this.$route.name === 'RECENT') { - return t('podcast', 'Stations you recently played will show up here') - } else if (this.$route.name === 'SEARCH') { - return t('podcast', 'No stations were found matching your search term') - } - return t('podcast', 'No stations here') - }, }, watch: { $route: 'onRoute', @@ -149,15 +109,18 @@ export default { } }, }, - created() { - this.loadSettings() - }, mounted() { - this.onRoute() + this.pageLoading = true + const podcastId = this.$route.params.id + this.queryPodcast(podcastId) this.scroll() }, methods: { + getCategoryName(categoryid) { + return getCategoryName(categoryid) + }, + onResize({ width, height }) { const contentHeight = document.getElementById('app-content-vue').scrollHeight const tableHeight = height @@ -166,56 +129,20 @@ export default { } }, + doPlay(episode) { + doPlay(episode) + }, + preFill() { const route = this.$route console.log(route) // this.queryPodcast(route.name) }, - async onRoute() { - this.tableData = [] - this.pageLoading = true - const podcastId = this.$route.params.id - this.queryPodcast(podcastId) - }, - /** * Start playing a podcast episode * @param {Object} episode Episode object */ - async doPlay(episode) { - const vm = this - - vm.$store.dispatch('isBuffering', true) - - if (audioPlayer !== null) { - audioPlayer.fade(vm.player.volume, 0, 500) - } - vm.$store.dispatch('setTitle', episode.title) - - Howler.unload() - audioPlayer = new Howl({ - src: episode.enclosure, - html5: true, - volume: vm.player.volume, - onplay() { - vm.$store.dispatch('isPlaying', true) - vm.$store.dispatch('isBuffering', false) - }, - onpause() { - vm.$store.dispatch('isPlaying', false) - vm.$store.dispatch('isBuffering', false) - }, - onend() { - showError(t('podcast', 'Lost connection to podcast station, retrying ...')) - vm.$store.dispatch('isPlaying', false) - vm.$store.dispatch('isBuffering', true) - }, - }) - audioPlayer.unload() - audioPlayer.play() - audioPlayer.fade(0, vm.player.volume, 500) - }, async queryPodcast(podcastId) { @@ -247,52 +174,48 @@ export default { const route = this.$route console.log(route) // this.queryPodcast(route.name) - } + } } }, - loadSettings() { - - // axios.defaults.headers.common = { - // 'User-Agent': 'Nextcloud Podcast App/' + this.$version, - // } - this.$store.dispatch('getVolumeState') - - }, - }, } </script> <style lang="scss"> -@media only screen and (min-width: 1024px) { - .app-navigation-toggle { - display: none; - } +.podcastHeaderBg { + background-size: cover; + background-position: center; } .podcastHeader { width: 100%; - background: black; min-height: 300px; display: flex; color: white; justify-content: center; padding: 40px 20px; - gap: 30px; + /* 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: 230px; - height: 230px; + width: 200px; + height: 200px; background-size: cover; - background-position: center center; + background-position: center; + box-shadow: 0 4px 60px rgba(0,0,0,.5); + margin-right: 25px; + border-radius: 5px; } .podcastDescription { max-width: 500px; - max-height: 200px; - overflow: hidden; - text-overflow: ellipsis; color: #ddd; h1 { @@ -303,24 +226,59 @@ export default { } .podcastAuthor { - padding-bottom: 10px; + padding-bottom: 5px; a { color: white; } } + .podcastControls { + display: flex; + align-items: center; + margin-bottom: 5px; + + .button { + margin-right: 10px; + } + } + ul.podcastCategory li { - color: var(--color-text-maxcontrast); display: inline; border: 1px solid var(--color-text-maxcontrast); border-radius: var(--border-radius); - padding: 3px 6px; margin-right: 5px; + cursor: pointer; + color: var(--color-text-maxcontrast); + padding: 3px 6px; } + ul.podcastCategory li:hover { + border: 1px solid white; + color: white; + } } } +@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