2022-03-23 17:59:14 -05:00
< template >
2024-02-04 15:46:26 -06:00
< div v-if = "!libraryItem" class="w-full h-full relative flex items-center justify-center bg-bg" >
< ui-loading-indicator / >
< / div >
< div v -else id = "item-page" class = "w-full h-full overflow-y-auto overflow-x-hidden relative bg-bg" >
2024-01-01 09:57:51 -06:00
<!-- cover -- >
< div class = "w-full flex justify-center relative" >
< div style = "width: 0; transform: translateX(-50vw); overflow: visible" >
< div style = "width: 150vw; overflow: hidden" >
< div id = "coverBg" style = "filter: blur(5vw)" >
< covers-book-cover :library-item = "libraryItem" :width = "coverWidth" :book-cover-aspect-ratio = "bookCoverAspectRatio" @imageLoaded ="coverImageLoaded" / >
2023-02-09 23:57:59 +01:00
< / div >
< / div >
2022-03-23 17:59:14 -05:00
< / div >
2024-01-01 09:57:51 -06:00
< div class = "relative" @ click = "showFullscreenCover = true" >
< covers-book-cover :library-item = "libraryItem" :width = "coverWidth" :book-cover-aspect-ratio = "bookCoverAspectRatio" no -bg raw @imageLoaded ="coverImageLoaded" / >
< div v-if = "!isPodcast" class="absolute bottom-0 left-0 h-1 shadow-sm z-10" :class="userIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: coverWidth * progressPercent + 'px' }" > < / div >
< / div >
< / div >
2022-03-23 17:59:14 -05:00
2024-01-01 09:57:51 -06:00
< div class = "relative" >
<!-- background gradient -- >
< div id = "item-page-bg-gradient" class = "absolute top-0 left-0 w-full pointer-events-none z-0" : style = "{ opacity: coverRgb ? 1 : 0 }" >
< div class = "w-full h-full" : style = "{ backgroundColor: coverRgb }" / >
< div class = "w-full h-full absolute top-0 left-0" style = "background: var(--gradient-item-page)" / >
2023-02-26 00:34:58 +01:00
< / div >
2022-12-03 17:43:47 -06:00
2024-01-01 09:57:51 -06:00
< div class = "relative z-10 px-3 py-4" >
<!-- title -- >
< div class = "text-center mb-2" >
2024-04-28 16:34:31 -05:00
< div class = "flex items-center justify-center" >
< h1 class = "text-xl font-semibold" > { { title } } < / h1 >
< widgets-explicit-indicator v-if = "isExplicit" / >
< / div >
2024-01-01 09:57:51 -06:00
< p v-if = "subtitle" class="text-fg text-base" > {{ subtitle }} < / p >
2023-11-14 17:13:49 -06:00
< / div >
2024-01-01 09:57:51 -06:00
< div v-if = "hasLocal" class="mx-1" >
2025-02-08 09:09:18 -07:00
< div v-if = "currentServerConnectionConfigId && !isLocalMatchingServerAddress" class="w-full rounded-md bg-warning/10 border border-warning p-4" >
2024-01-01 09:57:51 -06:00
< p class = "text-sm" > { { $getString ( 'MessageMediaLinkedToADifferentServer' , [ localLibraryItem . serverAddress ] ) } } < / p >
< / div >
< div v -else -if = " currentServerConnectionConfigId & & ! isLocalMatchingUser " class = "w-full rounded-md bg-warning/10 border border-warning p-4" >
< p class = "text-sm" > { { $strings . MessageMediaLinkedToADifferentUser } } < / p >
< / div >
< div v -else -if = " currentServerConnectionConfigId & & ! isLocalMatchingConnectionConfig " class = "w-full rounded-md bg-warning/10 border border-warning p-4" >
< p class = "text-sm" > Media is linked to a different server connection config . Downloaded User Id : { { localLibraryItem . serverUserId } } . Downloaded Server Address : { { localLibraryItem . serverAddress } } . Currently connected User Id : { { user . id } } . Currently connected server address : { { currentServerAddress } } . < / p >
< / div >
2023-11-14 17:13:49 -06:00
< / div >
2022-06-02 17:57:37 -05:00
2024-01-01 09:57:51 -06:00
<!-- action buttons -- >
< div class = "col-span-full" >
< div v-if = "showPlay || showRead" class="flex mt-4 -mx-1" >
< ui-btn v-if = "showPlay" color="success" class="flex items-center justify-center flex-grow mx-1" :loading="playerIsStartingForThisMedia" :padding-x="4" @click="playClick" >
2025-03-31 00:02:45 -07:00
< span class = "material-symbols text-2xl fill" > { { playerIsPlaying ? 'pause' : 'play_arrow' } } < / span >
2024-01-01 09:57:51 -06:00
< span class = "px-1 text-sm" > { { playerIsPlaying ? $strings . ButtonPause : isPodcast ? $strings . ButtonNextEpisode : hasLocal ? $strings . ButtonPlay : $strings . ButtonStream } } < / span >
< / ui-btn >
< ui-btn v-if = "showRead" color="info" class="flex items-center justify-center mx-1" :class="showPlay ? '' : 'flex-grow'" :padding-x="2" @click="readBook" >
2025-03-31 00:02:45 -07:00
< span class = "material-symbols text-2xl" > auto _stories < / span >
2024-01-01 09:57:51 -06:00
< span v-if = "!showPlay" class="px-2 text-base" > {{ $ strings.ButtonRead }} {{ ebookFormat }} < / span >
< / ui-btn >
< ui-btn v-if = "showDownload" :color="downloadItem ? 'warning' : 'primary'" class="flex items-center justify-center mx-1" :padding-x="2" @click="downloadClick" >
2025-03-31 00:02:45 -07:00
< span class = "material-symbols text-2xl" : class = "downloadItem || startingDownload ? 'animate-pulse' : ''" > { { downloadItem || startingDownload ? 'downloading' : 'download' } } < / span >
2024-01-01 09:57:51 -06:00
< / ui-btn >
< ui-btn color = "primary" class = "flex items-center justify-center mx-1" :padding-x = "2" @click ="moreButtonPress" >
2025-03-31 00:02:45 -07:00
< span class = "material-symbols text-2xl" > more _vert < / span >
2024-01-01 09:57:51 -06:00
< / ui-btn >
< / div >
2025-01-26 15:34:54 -06:00
< ui-btn v -else -if = " isMissing " color = "error" :padding-x = "4" small class = "mt-4 flex items-center justify-center w-full" @click ="clickMissingButton" >
2025-03-30 23:26:14 -07:00
< span class = "material-symbols" > error < / span >
2025-01-26 15:34:54 -06:00
< span class = "px-1 text-base" > { { $strings . LabelMissing } } < / span >
< / ui-btn >
2024-01-01 09:57:51 -06:00
< div v-if = "!isPodcast && progressPercent > 0" class="px-4 py-2 bg-primary text-sm font-semibold rounded-md text-fg mt-4 text-center" >
< p > { { $strings . LabelYourProgress } } : { { Math . round ( progressPercent * 100 ) } } % < / p >
< p v-if = "!useEBookProgress && !userIsFinished" class="text-fg-muted text-xs" > {{ $ getString ( ' LabelTimeRemaining ' , [ $ elapsedPretty ( userTimeRemaining ) ] ) }} < / p >
< p v -else -if = " userIsFinished " class = "text-fg-muted text-xs" > { { $strings . LabelFinished } } { { $formatDate ( userProgressFinishedAt ) } } < / p >
< / div >
2023-01-13 17:30:48 -06:00
< / div >
2023-02-26 00:34:58 +01:00
2024-01-01 09:57:51 -06:00
< div v-if = "downloadItem" class="py-3" >
< p v-if = "downloadItem.itemProgress == 1" class="text-center text-lg" > {{ $ strings.MessageDownloadCompleteProcessing }} < / p >
< p v -else class = "text-center text-lg" > { { $strings . MessageDownloading } } ( { { Math . round ( downloadItem . itemProgress * 100 ) } } % ) < / p >
2023-02-26 00:34:58 +01:00
< / div >
2023-01-13 17:30:48 -06:00
2024-01-01 09:57:51 -06:00
<!-- metadata -- >
< div id = "metadata" class = "grid gap-2 my-2" style >
2024-01-01 12:08:15 -06:00
< div v-if = "podcastAuthor || bookAuthors?.length" class="text-fg-muted uppercase text-sm" > {{ $ strings.LabelAuthor }} < / div >
2024-01-01 09:57:51 -06:00
< div v-if = "podcastAuthor" class="text-sm" > {{ podcastAuthor }} < / div >
2024-01-01 12:08:15 -06:00
< div v -else -if = " bookAuthors ? .length " class = "text-sm" >
2024-01-01 09:57:51 -06:00
< template v-for = "(author, index) in bookAuthors" >
2024-01-01 12:08:15 -06:00
< nuxt-link :key = "author.id" :to = "`/bookshelf/library?filter=authors.${$encode(author.id)}`" class = "underline whitespace-nowrap" > { { author . name } } < / n u x t - l i n k
2024-01-01 09:57:51 -06:00
> < span :key = "`${author.id}-comma`" v-if = "index < bookAuthors.length - 1" > , < / span >
< / template >
< / div >
2023-01-28 11:58:16 -06:00
2024-01-01 09:57:51 -06:00
< div v-if = "podcastType" class="text-fg-muted uppercase text-sm" > {{ $ strings.LabelType }} < / div >
< div v-if = "podcastType" class="text-sm capitalize" > {{ podcastType }} < / div >
2023-02-13 00:14:04 +01:00
2024-01-01 12:08:15 -06:00
< div v-if = "series?.length" class="text-fg-muted uppercase text-sm" > {{ $ strings.LabelSeries }} < / div >
< div v-if = "series?.length" class="text-sm" >
2024-01-01 09:57:51 -06:00
< template v-for = "(series, index) in seriesList" >
2024-01-01 12:08:15 -06:00
< nuxt-link :key = "series.id" :to = "`/bookshelf/series/${series.id}`" class = "underline whitespace-nowrap" > { { series . text } } < / n u x t - l i n k
2024-01-01 09:57:51 -06:00
> < span :key = "`${series.id}-comma`" v-if = "index < seriesList.length - 1" > , < / span >
< / template >
< / div >
2023-05-19 17:57:36 -05:00
2024-01-01 09:57:51 -06:00
< div v-if = "numTracks" class="text-fg-muted uppercase text-sm" > {{ $ strings.LabelDuration }} < / div >
< div v-if = "numTracks" class="text-sm" > {{ $ elapsedPretty ( duration ) }} < / div >
2023-02-12 22:51:04 +01:00
2024-01-01 12:08:15 -06:00
< div v-if = "narrators?.length" class="text-fg-muted uppercase text-sm" > {{ $ strings.LabelNarrators }} < / div >
< div v-if = "narrators?.length" class="text-sm" >
2024-01-01 09:57:51 -06:00
< template v-for = "(narrator, index) in narrators" >
2024-01-01 12:08:15 -06:00
< nuxt-link :key = "narrator" :to = "`/bookshelf/library?filter=narrators.${$encode(narrator)}`" class = "underline whitespace-nowrap" > { { narrator } } < / n u x t - l i n k
2024-01-01 09:57:51 -06:00
> < span :key = "index" v-if = "index < narrators.length - 1" > , < / span >
< / template >
< / div >
2023-02-26 00:34:58 +01:00
2024-01-01 09:57:51 -06:00
< div v-if = "genres.length" class="text-fg-muted uppercase text-sm" > {{ $ strings.LabelGenres }} < / div >
2024-01-01 12:08:15 -06:00
< div v-if = "genres.length" class="text-sm" >
2024-01-01 09:57:51 -06:00
< template v-for = "(genre, index) in genres" >
2024-01-01 12:08:15 -06:00
< nuxt-link :key = "genre" :to = "`/bookshelf/library?filter=genres.${$encode(genre)}`" class = "underline whitespace-nowrap" > { { genre } } < / n u x t - l i n k
2024-01-01 09:57:51 -06:00
> < span :key = "index" v-if = "index < genres.length - 1" > , < / span >
< / template >
< / div >
2022-06-04 10:19:31 -05:00
2024-01-01 12:08:15 -06:00
< div v-if = "tags.length" class="text-fg-muted uppercase text-sm" > {{ $ strings.LabelTags }} < / div >
< div v-if = "tags.length" class="text-sm" >
< template v-for = "(tag, index) in tags" >
< nuxt-link :key = "tag" :to = "`/bookshelf/library?filter=tags.${$encode(tag)}`" class = "underline whitespace-nowrap" > { { tag } } < / n u x t - l i n k
> < span :key = "index" v-if = "index < tags.length - 1" > , < / span >
< / template >
< / div >
2024-01-01 09:57:51 -06:00
< div v-if = "publishedYear" class="text-fg-muted uppercase text-sm" > {{ $ strings.LabelPublishYear }} < / div >
< div v-if = "publishedYear" class="text-sm" > {{ publishedYear }} < / div >
2023-01-11 18:00:05 -06:00
< / div >
2023-01-08 17:04:08 +01:00
2024-01-01 09:57:51 -06:00
< div v-if = "description" class="w-full py-2" >
2025-01-25 14:10:35 -06:00
< div ref = "description" class = "default-style less-spacing text-sm text-justify whitespace-pre-line font-light" : class = "{ 'line-clamp-4': !showFullDescription }" style = "hyphens: auto" v-html = "description" / >
2023-03-06 15:27:12 -06:00
2024-01-01 09:57:51 -06:00
< div v-if = "descriptionClamped" class="text-fg text-sm py-2" @click="showFullDescription = !showFullDescription" >
2024-11-14 17:51:16 -06:00
{ { showFullDescription ? $strings . ButtonReadLess : $strings . ButtonReadMore } }
2025-04-05 17:14:43 -05:00
< span class = "material-symbols !align-middle text-base -mt-px" > { { showFullDescription ? 'arrow_drop_up' : 'arrow_drop_down' } } < / span >
2024-01-01 09:57:51 -06:00
< / div >
2023-03-06 15:27:12 -06:00
< / div >
2022-04-02 19:43:43 -05:00
2024-01-01 09:57:51 -06:00
<!-- tables -- >
< tables-podcast-episodes-table v-if = "isPodcast" :library-item="libraryItem" :local-library-item-id="localLibraryItemId" :episodes="episodes" :local-episodes="localLibraryItemEpisodes" :is-local="isLocal" / >
2022-04-10 20:31:47 -05:00
2024-01-01 09:57:51 -06:00
< tables-chapters-table v-if = "numChapters" :library-item="libraryItem" @playAtTimestamp="playAtTimestamp" / >
2023-03-06 15:27:12 -06:00
2024-01-01 09:57:51 -06:00
< tables-tracks-table v-if = "numTracks" :tracks="tracks" :library-item-id="libraryItemId" / >
2023-03-06 15:27:12 -06:00
2024-01-01 09:57:51 -06:00
< tables-ebook-files-table v-if = "ebookFiles.length" :library-item="libraryItem" / >
< / div >
< / div >
2023-06-11 13:36:19 -05:00
2024-01-01 09:57:51 -06:00
<!-- modals -- >
< modals-item-more-menu-modal v-model = "showMoreMenu" :library-item="libraryItem" :rss-feed="rssFeed" :processing.sync="processing" / >
2022-06-02 17:57:37 -05:00
2024-01-01 09:57:51 -06:00
< modals-select-local-folder-modal v-model = "showSelectLocalFolder" :media-type="mediaType" @select="selectedLocalFolder" / >
2022-06-24 17:20:13 -05:00
2024-01-01 09:57:51 -06:00
< modals-fullscreen-cover v-model = "showFullscreenCover" :library-item="libraryItem" / >
2023-11-02 16:10:55 -05:00
< div v-show = "processing" class="fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-black/50 z-50" >
< ui-loading-indicator / >
< / div >
2022-03-23 17:59:14 -05:00
< / div >
< / template >
< script >
import { Dialog } from '@capacitor/dialog'
2022-04-04 19:08:27 -05:00
import { AbsFileSystem , AbsDownloader } from '@/plugins/capacitor'
2023-01-11 18:00:05 -06:00
import { FastAverageColor } from 'fast-average-color'
2024-05-26 22:08:53 +01:00
import cellularPermissionHelpers from '@/mixins/cellularPermissionHelpers'
2022-03-23 17:59:14 -05:00
export default {
2024-02-04 15:46:26 -06:00
async asyncData ( { store , params , redirect , app , query } ) {
2023-02-25 16:33:06 -06:00
const libraryItemId = params . id
let libraryItem = null
2024-02-04 15:46:26 -06:00
2022-04-03 17:07:26 -05:00
if ( libraryItemId . startsWith ( 'local' ) ) {
libraryItem = await app . $db . getLocalLibraryItem ( libraryItemId )
2024-02-04 15:46:26 -06:00
if ( ! libraryItem ) {
return redirect ( '/?error=Failed to get downloaded library item' )
}
2023-11-14 17:13:49 -06:00
// If library item is linked to the currently connected server then redirect to the page using the server library item id
2023-11-25 14:38:40 -06:00
if ( libraryItem ? . libraryItemId ? . startsWith ( 'li_' ) ) {
// Detect old library item id
console . error ( 'Local library item has old server library item id' , libraryItem . libraryItemId )
2024-11-03 21:45:05 +02:00
} else if ( query . noredirect !== '1' && libraryItem ? . libraryItemId && libraryItem ? . serverAddress === store . getters [ 'user/getServerAddress' ] && store . state . socketConnected ) {
2024-02-04 15:46:26 -06:00
const queryParams = new URLSearchParams ( )
queryParams . set ( 'localLibraryItemId' , libraryItemId )
if ( libraryItem . mediaType === 'podcast' ) {
// Filter by downloaded when redirecting from the local copy
queryParams . set ( 'episodefilter' , 'downloaded' )
2022-04-07 18:46:58 -05:00
}
2024-02-04 15:46:26 -06:00
return redirect ( ` /item/ ${ libraryItem . libraryItemId } ? ${ queryParams . toString ( ) } ` )
2022-04-07 18:46:58 -05:00
}
2024-02-04 15:46:26 -06:00
} else if ( ! store . state . user . serverConnectionConfig ) {
// Not connected to server
return redirect ( '/?error=No server connection to get library item' )
2022-03-23 17:59:14 -05:00
}
return {
2023-06-24 14:45:25 -05:00
libraryItem ,
2024-02-04 15:46:26 -06:00
libraryItemId
2022-03-23 17:59:14 -05:00
}
} ,
data ( ) {
return {
2023-11-02 16:10:55 -05:00
processing : false ,
2022-06-02 17:57:37 -05:00
showSelectLocalFolder : false ,
showMoreMenu : false ,
2023-01-11 18:00:05 -06:00
showFullscreenCover : false ,
2024-01-01 09:57:51 -06:00
coverRgb : null ,
2023-01-12 17:04:47 -06:00
coverBgIsLight : false ,
2023-02-26 12:10:18 +01:00
windowWidth : 0 ,
2023-03-06 15:27:12 -06:00
descriptionClamped : false ,
2023-12-15 17:35:37 -06:00
showFullDescription : false ,
2023-12-16 17:21:19 -06:00
episodeStartingPlayback : null ,
startingDownload : false
2022-03-23 17:59:14 -05:00
}
} ,
2024-05-26 22:08:53 +01:00
mixins : [ cellularPermissionHelpers ] ,
2022-03-23 17:59:14 -05:00
computed : {
isIos ( ) {
return this . $platform === 'ios'
} ,
2022-05-22 15:49:42 -05:00
userCanDownload ( ) {
return this . $store . getters [ 'user/getUserCanDownload' ]
} ,
2023-06-24 14:45:25 -05:00
userIsAdminOrUp ( ) {
return this . $store . getters [ 'user/getIsAdminOrUp' ]
} ,
2022-04-03 17:07:26 -05:00
isLocal ( ) {
return this . libraryItem . isLocal
} ,
2022-04-07 18:46:58 -05:00
hasLocal ( ) {
// Server library item has matching local library item
return this . isLocal || this . libraryItem . localLibraryItem
} ,
localLibraryItem ( ) {
if ( this . isLocal ) return this . libraryItem
return this . libraryItem . localLibraryItem || null
} ,
2022-04-15 20:48:39 -05:00
localLibraryItemId ( ) {
2023-05-20 16:29:54 -05:00
return this . localLibraryItem ? . id || null
2022-04-15 20:48:39 -05:00
} ,
localLibraryItemEpisodes ( ) {
if ( ! this . isPodcast || ! this . localLibraryItem ) return [ ]
var podcastMedia = this . localLibraryItem . media
2023-05-20 16:29:54 -05:00
return podcastMedia ? . episodes || [ ]
2022-04-15 20:48:39 -05:00
} ,
2022-05-04 19:31:56 -05:00
serverLibraryItemId ( ) {
if ( ! this . isLocal ) return this . libraryItem . id
// Check if local library item is connected to the current server
if ( ! this . libraryItem . serverAddress || ! this . libraryItem . libraryItemId ) return null
2023-11-14 17:13:49 -06:00
if ( this . currentServerAddress === this . libraryItem . serverAddress ) {
2022-05-04 19:31:56 -05:00
return this . libraryItem . libraryItemId
}
return null
2022-03-23 17:59:14 -05:00
} ,
2023-11-14 17:13:49 -06:00
localLibraryItemServerConnectionConfigId ( ) {
return this . localLibraryItem ? . serverConnectionConfigId
} ,
currentServerAddress ( ) {
return this . $store . getters [ 'user/getServerAddress' ]
} ,
currentServerConnectionConfigId ( ) {
return this . $store . getters [ 'user/getServerConnectionConfigId' ]
} ,
/ * *
* User is currently connected to a server and this local library item has the same server address
* /
isLocalMatchingServerAddress ( ) {
2025-02-08 09:09:18 -07:00
if ( ! this . localLibraryItem || ! this . currentServerAddress ) return false
2023-11-14 17:13:49 -06:00
return this . localLibraryItem . serverAddress === this . currentServerAddress
} ,
2023-11-16 13:28:12 -06:00
/ * *
* User is currently connected to a server and this local library item has the same user id
* /
isLocalMatchingUser ( ) {
2025-02-08 09:09:18 -07:00
if ( ! this . localLibraryItem || ! this . user ) return false
2023-11-21 10:55:26 -06:00
return this . localLibraryItem . serverUserId === this . user . id || this . localLibraryItem . serverUserId === this . user . oldUserId
2023-11-16 13:28:12 -06:00
} ,
2023-11-14 17:13:49 -06:00
/ * *
* User is currently connected to a server and this local library item has the same connection config id
* /
isLocalMatchingConnectionConfig ( ) {
2025-02-08 09:09:18 -07:00
if ( ! this . localLibraryItemServerConnectionConfigId || ! this . currentServerConnectionConfigId ) return false
2023-11-14 17:13:49 -06:00
return this . localLibraryItemServerConnectionConfigId === this . currentServerConnectionConfigId
} ,
2022-03-23 17:59:14 -05:00
bookCoverAspectRatio ( ) {
2022-10-22 08:59:10 -05:00
return this . $store . getters [ 'libraries/getBookCoverAspectRatio' ]
2022-03-23 17:59:14 -05:00
} ,
2024-02-04 15:46:26 -06:00
rssFeed ( ) {
return this . libraryItem ? . rssFeed
2022-03-23 17:59:14 -05:00
} ,
2022-04-02 19:43:43 -05:00
mediaType ( ) {
return this . libraryItem . mediaType
} ,
2022-04-10 20:31:47 -05:00
isPodcast ( ) {
return this . mediaType == 'podcast'
} ,
2022-03-23 17:59:14 -05:00
media ( ) {
return this . libraryItem . media || { }
} ,
2024-01-01 12:08:15 -06:00
tags ( ) {
return this . media . tags || [ ]
} ,
2022-03-23 17:59:14 -05:00
mediaMetadata ( ) {
return this . media . metadata || { }
} ,
title ( ) {
return this . mediaMetadata . title
} ,
2022-08-07 10:35:01 -05:00
subtitle ( ) {
return this . mediaMetadata . subtitle
} ,
2022-12-03 17:43:47 -06:00
genres ( ) {
return this . mediaMetadata . genres || [ ]
} ,
publishedYear ( ) {
return this . mediaMetadata . publishedYear
} ,
2023-05-19 17:57:36 -05:00
podcastType ( ) {
return this . mediaMetadata . type
} ,
2022-06-04 10:19:31 -05:00
podcastAuthor ( ) {
if ( ! this . isPodcast ) return null
return this . mediaMetadata . author || ''
} ,
bookAuthors ( ) {
if ( this . isPodcast ) return null
return this . mediaMetadata . authors || [ ]
2022-03-23 17:59:14 -05:00
} ,
2022-06-04 10:19:31 -05:00
narrators ( ) {
2022-06-02 17:57:37 -05:00
if ( this . isPodcast ) return null
2022-06-04 10:19:31 -05:00
return this . mediaMetadata . narrators || [ ]
2022-06-02 17:57:37 -05:00
} ,
2022-03-23 17:59:14 -05:00
description ( ) {
return this . mediaMetadata . description || ''
} ,
series ( ) {
return this . mediaMetadata . series || [ ]
} ,
2022-06-04 10:19:31 -05:00
seriesList ( ) {
if ( this . isPodcast ) return null
return this . series . map ( ( se ) => {
var text = se . name
if ( se . sequence ) text += ` # ${ se . sequence } `
return {
... se ,
text
}
} )
2022-05-04 19:31:56 -05:00
} ,
2022-03-23 17:59:14 -05:00
duration ( ) {
2022-03-28 19:53:53 -05:00
return this . media . duration
2022-03-23 17:59:14 -05:00
} ,
2022-04-07 18:46:58 -05:00
user ( ) {
return this . $store . state . user . user
} ,
2022-03-23 17:59:14 -05:00
userItemProgress ( ) {
2023-02-07 16:44:23 -06:00
if ( this . isPodcast ) return null
2023-05-20 16:29:54 -05:00
if ( this . isLocal ) return this . localItemProgress
return this . serverItemProgress
} ,
localItemProgress ( ) {
if ( this . isPodcast ) return null
return this . $store . getters [ 'globals/getLocalMediaProgressById' ] ( this . localLibraryItemId )
} ,
serverItemProgress ( ) {
if ( this . isPodcast ) return null
return this . $store . getters [ 'user/getUserMediaProgress' ] ( this . serverLibraryItemId )
2022-03-23 17:59:14 -05:00
} ,
userIsFinished ( ) {
2023-11-03 15:53:38 -05:00
return ! ! this . userItemProgress ? . isFinished
2022-03-23 17:59:14 -05:00
} ,
userTimeRemaining ( ) {
if ( ! this . userItemProgress ) return 0
2023-01-08 14:50:21 -06:00
const duration = this . userItemProgress . duration || this . duration
2022-03-23 17:59:14 -05:00
return duration - this . userItemProgress . currentTime
} ,
2023-03-25 17:40:46 -05:00
useEBookProgress ( ) {
if ( ! this . userItemProgress || this . userItemProgress . progress ) return false
return this . userItemProgress . ebookProgress > 0
} ,
2022-03-23 17:59:14 -05:00
progressPercent ( ) {
2023-03-25 17:40:46 -05:00
if ( this . useEBookProgress ) return Math . max ( Math . min ( 1 , this . userItemProgress . ebookProgress ) , 0 )
2023-11-03 15:53:38 -05:00
return Math . max ( Math . min ( 1 , this . userItemProgress ? . progress || 0 ) , 0 )
2022-03-23 17:59:14 -05:00
} ,
userProgressFinishedAt ( ) {
2023-11-03 15:53:38 -05:00
return this . userItemProgress ? . finishedAt || 0
2022-03-23 17:59:14 -05:00
} ,
isStreaming ( ) {
2023-06-19 12:37:44 -05:00
return this . isPlaying && ! this . $store . getters [ 'getIsCurrentSessionLocal' ]
2022-03-23 17:59:14 -05:00
} ,
isPlaying ( ) {
2023-06-19 12:37:44 -05:00
if ( this . localLibraryItemId && this . $store . getters [ 'getIsMediaStreaming' ] ( this . localLibraryItemId ) ) return true
return this . $store . getters [ 'getIsMediaStreaming' ] ( this . libraryItemId )
2022-03-23 17:59:14 -05:00
} ,
2023-05-20 14:49:55 -05:00
playerIsPlaying ( ) {
return this . $store . state . playerIsPlaying && ( this . isStreaming || this . isPlaying )
} ,
2023-12-15 17:35:37 -06:00
playerIsStartingPlayback ( ) {
// Play has been pressed and waiting for native play response
return this . $store . state . playerIsStartingPlayback
} ,
playerIsStartingForThisMedia ( ) {
const mediaId = this . $store . state . playerStartingPlaybackMediaId
2023-12-18 17:05:34 -06:00
if ( ! mediaId ) return false
2023-12-15 17:35:37 -06:00
if ( this . isPodcast ) {
return mediaId === this . episodeStartingPlayback
} else {
2023-12-18 17:05:34 -06:00
return mediaId === this . serverLibraryItemId || mediaId === this . localLibraryItemId
2023-12-15 17:35:37 -06:00
}
} ,
2023-03-06 15:27:12 -06:00
tracks ( ) {
return this . media . tracks || [ ]
} ,
2022-03-23 17:59:14 -05:00
numTracks ( ) {
2023-03-06 15:27:12 -06:00
return this . tracks . length || 0
2022-03-23 17:59:14 -05:00
} ,
2022-06-02 17:57:37 -05:00
numChapters ( ) {
if ( ! this . media . chapters ) return 0
return this . media . chapters . length || 0
} ,
2022-03-23 17:59:14 -05:00
isMissing ( ) {
return this . libraryItem . isMissing
} ,
2023-05-20 14:49:55 -05:00
isInvalid ( ) {
return this . libraryItem . isInvalid
2022-03-23 17:59:14 -05:00
} ,
2024-04-28 16:34:31 -05:00
isExplicit ( ) {
return ! ! this . mediaMetadata . explicit
} ,
2022-03-23 17:59:14 -05:00
showPlay ( ) {
2023-05-20 14:49:55 -05:00
return ! this . isMissing && ! this . isInvalid && ( this . numTracks || this . episodes . length )
2022-03-23 17:59:14 -05:00
} ,
showRead ( ) {
2022-12-06 17:07:27 -06:00
return this . ebookFile
2022-03-23 17:59:14 -05:00
} ,
2022-05-22 15:49:42 -05:00
showDownload ( ) {
2023-05-20 14:49:55 -05:00
if ( this . isPodcast || this . hasLocal ) return false
2023-06-19 17:42:15 -05:00
return this . user && this . userCanDownload && ( this . showPlay || this . showRead )
2022-05-22 15:49:42 -05:00
} ,
2023-06-11 13:36:19 -05:00
libraryFiles ( ) {
return this . libraryItem . libraryFiles || [ ]
} ,
ebookFiles ( ) {
return this . libraryFiles . filter ( ( lf ) => lf . fileType === 'ebook' )
} ,
2022-03-28 19:53:53 -05:00
ebookFile ( ) {
return this . media . ebookFile
2022-03-23 17:59:14 -05:00
} ,
ebookFormat ( ) {
2022-03-28 19:53:53 -05:00
if ( ! this . ebookFile ) return null
return this . ebookFile . ebookFormat
2022-03-23 17:59:14 -05:00
} ,
2022-04-07 18:46:58 -05:00
downloadItem ( ) {
return this . $store . getters [ 'globals/getDownloadItem' ] ( this . libraryItemId )
} ,
2022-04-10 20:31:47 -05:00
episodes ( ) {
return this . media . episodes || [ ]
2022-05-04 19:31:56 -05:00
} ,
isCasting ( ) {
return this . $store . state . isCasting
2022-06-02 17:57:37 -05:00
} ,
2023-01-12 17:04:47 -06:00
coverWidth ( ) {
let width = this . windowWidth - 94
if ( width > 325 ) return 325
else if ( width < 0 ) return 175
if ( width * this . bookCoverAspectRatio > 325 ) width = 325 / this . bookCoverAspectRatio
return width
2024-01-01 09:57:51 -06:00
} ,
coverHeight ( ) {
return this . coverWidth * this . bookCoverAspectRatio
2022-03-23 17:59:14 -05:00
}
} ,
methods : {
2025-01-26 15:34:54 -06:00
clickMissingButton ( ) {
Dialog . alert ( {
title : this . $strings . LabelMissing ,
message : this . $strings . MessageItemMissing ,
cancelText : this . $strings . ButtonOk
} )
} ,
2023-01-11 18:00:05 -06:00
async coverImageLoaded ( fullCoverUrl ) {
if ( ! fullCoverUrl ) return
const fac = new FastAverageColor ( )
fac
. getColorAsync ( fullCoverUrl )
. then ( ( color ) => {
this . coverRgb = color . rgba
this . coverBgIsLight = color . isLight
} )
. catch ( ( e ) => {
console . log ( e )
} )
} ,
2022-06-02 17:57:37 -05:00
moreButtonPress ( ) {
this . showMoreMenu = true
} ,
2022-03-23 17:59:14 -05:00
readBook ( ) {
2023-05-21 15:02:49 -05:00
if ( this . localLibraryItem ? . media ? . ebookFile ) {
// Has local ebook file
2023-06-11 11:12:52 -05:00
this . $store . commit ( 'showReader' , { libraryItem : this . localLibraryItem , keepProgress : true } )
2023-05-21 15:02:49 -05:00
} else {
2023-06-11 11:12:52 -05:00
this . $store . commit ( 'showReader' , { libraryItem : this . libraryItem , keepProgress : true } )
2023-05-21 15:02:49 -05:00
}
2022-03-23 17:59:14 -05:00
} ,
2023-03-06 15:27:12 -06:00
playAtTimestamp ( seconds ) {
2023-03-06 16:59:49 -06:00
this . play ( seconds )
2023-03-06 15:27:12 -06:00
} ,
2023-05-20 14:49:55 -05:00
async playClick ( ) {
await this . $hapticsImpact ( )
if ( this . playerIsPlaying ) {
this . $eventBus . $emit ( 'pause-item' )
} else {
this . play ( )
}
2023-03-06 16:59:49 -06:00
} ,
async play ( startTime = null ) {
2023-12-15 17:35:37 -06:00
if ( this . playerIsStartingPlayback ) return
2022-08-11 17:36:27 -05:00
if ( this . isPodcast ) {
this . episodes . sort ( ( a , b ) => {
return String ( b . publishedAt ) . localeCompare ( String ( a . publishedAt ) , undefined , { numeric : true , sensitivity : 'base' } )
} )
2022-12-10 14:26:58 -06:00
let episode = this . episodes . find ( ( ep ) => {
2022-08-11 17:36:27 -05:00
var podcastProgress = null
if ( ! this . isLocal ) {
podcastProgress = this . $store . getters [ 'user/getUserMediaProgress' ] ( this . libraryItemId , ep . id )
} else {
podcastProgress = this . $store . getters [ 'globals/getLocalMediaProgressById' ] ( this . libraryItemId , ep . id )
}
2023-12-15 17:35:37 -06:00
return ! podcastProgress ? . isFinished
2022-08-11 17:36:27 -05:00
} )
if ( ! episode ) episode = this . episodes [ 0 ]
2023-03-06 15:27:12 -06:00
const episodeId = episode . id
2022-08-11 17:36:27 -05:00
2022-12-10 14:26:58 -06:00
let localEpisode = null
2022-08-11 17:36:27 -05:00
if ( this . hasLocal && ! this . isLocal ) {
localEpisode = this . localLibraryItem . media . episodes . find ( ( ep ) => ep . serverEpisodeId == episodeId )
} else if ( this . isLocal ) {
localEpisode = episode
}
2023-09-11 17:08:15 -05:00
const serverEpisodeId = ! this . isLocal ? episodeId : localEpisode ? . serverEpisodeId || null
2022-08-11 17:36:27 -05:00
2023-12-15 17:35:37 -06:00
this . episodeStartingPlayback = serverEpisodeId
this . $store . commit ( 'setPlayerIsStartingPlayback' , serverEpisodeId )
2022-08-11 17:36:27 -05:00
if ( serverEpisodeId && this . serverLibraryItemId && this . isCasting ) {
// If casting and connected to server for local library item then send server library item id
this . $eventBus . $emit ( 'play-item' , { libraryItemId : this . serverLibraryItemId , episodeId : serverEpisodeId } )
2023-03-06 15:27:12 -06:00
} else if ( localEpisode ) {
2022-08-11 17:36:27 -05:00
this . $eventBus . $emit ( 'play-item' , { libraryItemId : this . localLibraryItem . id , episodeId : localEpisode . id , serverLibraryItemId : this . serverLibraryItemId , serverEpisodeId } )
2023-03-06 15:27:12 -06:00
} else {
this . $eventBus . $emit ( 'play-item' , { libraryItemId : this . libraryItemId , episodeId } )
2022-08-11 17:36:27 -05:00
}
} else {
// Audiobook
2023-03-06 15:27:12 -06:00
let libraryItemId = this . libraryItemId
// When casting use server library item
2022-08-11 17:36:27 -05:00
if ( this . hasLocal && this . serverLibraryItemId && this . isCasting ) {
2023-03-06 15:27:12 -06:00
libraryItemId = this . serverLibraryItemId
} else if ( this . hasLocal ) {
libraryItemId = this . localLibraryItem . id
2022-08-11 17:36:27 -05:00
}
2023-03-06 15:27:12 -06:00
// If start time and is not already streaming then ask for confirmation
if ( startTime !== null && startTime !== undefined && ! this . $store . getters [ 'getIsMediaStreaming' ] ( libraryItemId , null ) ) {
const { value } = await Dialog . confirm ( {
title : 'Confirm' ,
message : ` Start playback for " ${ this . title } " at ${ this . $secondsToTimestamp ( startTime ) } ? `
} )
if ( ! value ) return
2022-08-11 17:36:27 -05:00
}
2023-12-18 17:05:34 -06:00
this . $store . commit ( 'setPlayerIsStartingPlayback' , libraryItemId )
2023-03-06 15:27:12 -06:00
this . $eventBus . $emit ( 'play-item' , { libraryItemId , serverLibraryItemId : this . serverLibraryItemId , startTime } )
}
2022-03-23 17:59:14 -05:00
} ,
itemUpdated ( libraryItem ) {
2023-06-24 14:45:25 -05:00
if ( libraryItem . id === this . serverLibraryItemId ) {
2022-03-23 17:59:14 -05:00
console . log ( 'Item Updated' )
this . libraryItem = libraryItem
2023-03-06 15:27:12 -06:00
this . checkDescriptionClamped ( )
2022-03-23 17:59:14 -05:00
}
} ,
2022-04-02 19:43:43 -05:00
async selectFolder ( ) {
// Select and save the local folder for media type
2022-04-04 19:08:27 -05:00
var folderObj = await AbsFileSystem . selectFolder ( { mediaType : this . mediaType } )
2022-04-02 19:43:43 -05:00
if ( folderObj . error ) {
return this . $toast . error ( ` Error: ${ folderObj . error || 'Unknown Error' } ` )
}
return folderObj
} ,
selectedLocalFolder ( localFolder ) {
this . showSelectLocalFolder = false
this . download ( localFolder )
} ,
2022-12-08 00:28:28 -05:00
async downloadClick ( ) {
2024-11-14 17:51:16 -06:00
if ( this . downloadItem || this . startingDownload ) return
2024-05-26 22:08:53 +01:00
const hasPermission = await this . checkCellularPermission ( 'download' )
if ( ! hasPermission ) return
2023-12-16 17:21:19 -06:00
this . startingDownload = true
setTimeout ( ( ) => {
this . startingDownload = false
} , 1000 )
2023-01-08 15:32:15 -06:00
await this . $hapticsImpact ( )
2022-05-14 09:08:52 -05:00
if ( this . isIos ) {
// no local folders on iOS
this . startDownload ( )
} else {
this . download ( )
}
} ,
async download ( selectedLocalFolder = null ) {
2022-04-02 19:43:43 -05:00
// Get the local folder to download to
2023-06-04 14:59:55 -05:00
let localFolder = selectedLocalFolder
2022-04-02 19:43:43 -05:00
if ( ! localFolder ) {
2023-06-04 14:59:55 -05:00
const localFolders = ( await this . $db . getLocalFolders ( ) ) || [ ]
2022-04-02 19:43:43 -05:00
console . log ( 'Local folders loaded' , localFolders . length )
2023-06-04 14:59:55 -05:00
const foldersWithMediaType = localFolders . filter ( ( lf ) => {
2022-04-02 19:43:43 -05:00
console . log ( 'Checking local folder' , lf . mediaType )
return lf . mediaType == this . mediaType
} )
console . log ( 'Folders with media type' , this . mediaType , foldersWithMediaType . length )
2023-06-04 14:59:55 -05:00
const internalStorageFolder = foldersWithMediaType . find ( ( f ) => f . id === ` internal- ${ this . mediaType } ` )
2022-04-02 19:43:43 -05:00
if ( ! foldersWithMediaType . length ) {
2023-06-03 17:24:32 -05:00
localFolder = {
id : ` internal- ${ this . mediaType } ` ,
2023-12-04 17:53:36 -06:00
name : this . $strings . LabelInternalAppStorage ,
2023-06-03 17:24:32 -05:00
mediaType : this . mediaType
}
2023-06-04 14:59:55 -05:00
} else if ( foldersWithMediaType . length === 1 && internalStorageFolder ) {
localFolder = internalStorageFolder
2022-04-02 19:43:43 -05:00
} else {
2023-06-04 14:59:55 -05:00
this . $store . commit ( 'globals/showSelectLocalFolderModal' , {
mediaType : this . mediaType ,
callback : ( folder ) => {
this . download ( folder )
}
} )
2022-04-02 19:43:43 -05:00
return
}
}
console . log ( 'Local folder' , JSON . stringify ( localFolder ) )
2023-12-08 07:15:14 +00:00
let startDownloadMessage = ` Start download for " ${ this . title } " with ${ this . numTracks } audio track ${ this . numTracks == 1 ? '' : 's' } to folder ${ localFolder . name } ? `
if ( ! this . isIos && this . showRead ) {
if ( this . numTracks > 0 ) {
startDownloadMessage = ` Start download for " ${ this . title } " with ${ this . numTracks } audio track ${ this . numTracks == 1 ? '' : 's' } and ebook file to folder ${ localFolder . name } ? `
} else {
startDownloadMessage = ` Start download for " ${ this . title } " with ebook file to folder ${ localFolder . name } ? `
2023-11-30 19:02:51 +00:00
}
2022-04-02 19:43:43 -05:00
}
2023-12-08 07:15:14 +00:00
const { value } = await Dialog . confirm ( {
title : 'Confirm' ,
message : startDownloadMessage
} )
if ( value ) {
this . startDownload ( localFolder )
}
2022-04-02 19:43:43 -05:00
} ,
2022-05-14 09:08:52 -05:00
async startDownload ( localFolder = null ) {
const payload = {
libraryItemId : this . libraryItemId
}
if ( localFolder ) {
console . log ( 'Starting download to local folder' , localFolder . name )
payload . localFolderId = localFolder . id
}
var downloadRes = await AbsDownloader . downloadLibraryItem ( payload )
2022-04-07 18:46:58 -05:00
if ( downloadRes && downloadRes . error ) {
2022-04-02 19:43:43 -05:00
var errorMsg = downloadRes . error || 'Unknown error'
console . error ( 'Download error' , errorMsg )
2022-04-03 19:16:17 -05:00
this . $toast . error ( errorMsg )
2022-03-23 17:59:14 -05:00
}
2022-04-07 18:46:58 -05:00
} ,
newLocalLibraryItem ( item ) {
if ( item . libraryItemId == this . libraryItemId ) {
console . log ( 'New local library item' , item . id )
this . $set ( this . libraryItem , 'localLibraryItem' , item )
}
2022-04-23 00:27:03 -04:00
} ,
2022-08-25 17:58:29 -05:00
libraryChanged ( libraryId ) {
if ( this . libraryItem . libraryId !== libraryId ) {
this . $router . replace ( '/bookshelf' )
}
2023-01-12 17:04:47 -06:00
} ,
2023-03-06 15:27:12 -06:00
checkDescriptionClamped ( ) {
2023-03-06 15:51:11 -06:00
if ( this . showFullDescription ) return
if ( ! this . $refs . description ) {
this . descriptionClamped = false
} else {
this . descriptionClamped = this . $refs . description . scrollHeight > this . $refs . description . clientHeight
}
2023-03-06 15:27:12 -06:00
} ,
2023-01-12 17:04:47 -06:00
windowResized ( ) {
this . windowWidth = window . innerWidth
2023-03-06 15:27:12 -06:00
this . checkDescriptionClamped ( )
2023-06-24 14:45:25 -05:00
} ,
rssFeedOpen ( data ) {
if ( data . entityId === this . serverLibraryItemId ) {
console . log ( 'RSS Feed Opened' , data )
this . rssFeed = data
}
} ,
rssFeedClosed ( data ) {
if ( data . entityId === this . serverLibraryItemId ) {
console . log ( 'RSS Feed Closed' , data )
this . rssFeed = null
}
2023-09-11 17:08:15 -05:00
} ,
async setLibrary ( ) {
if ( ! this . libraryItem . libraryId ) return
await this . $store . dispatch ( 'libraries/fetch' , this . libraryItem . libraryId )
this . $localStore . setLastLibraryId ( this . libraryItem . libraryId )
2024-02-04 15:46:26 -06:00
} ,
init ( ) {
// If library of this item is different from current library then switch libraries
if ( this . $store . state . libraries . currentLibraryId !== this . libraryItem . libraryId ) {
this . setLibrary ( )
}
this . windowWidth = window . innerWidth
window . addEventListener ( 'resize' , this . windowResized )
this . $eventBus . $on ( 'library-changed' , this . libraryChanged )
this . $eventBus . $on ( 'new-local-library-item' , this . newLocalLibraryItem )
this . $socket . $on ( 'item_updated' , this . itemUpdated )
this . $socket . $on ( 'rss_feed_open' , this . rssFeedOpen )
this . $socket . $on ( 'rss_feed_closed' , this . rssFeedClosed )
this . checkDescriptionClamped ( )
// Set height of page below cover image
const itemPageBgGradientHeight = window . outerHeight - 64 - this . coverHeight
document . documentElement . style . setProperty ( '--item-page-bg-gradient-height' , itemPageBgGradientHeight + 'px' )
// Set last scroll position if was set for this item
if ( this . $store . state . lastItemScrollData . id === this . libraryItemId && window [ 'item-page' ] ) {
window [ 'item-page' ] . scrollTop = this . $store . state . lastItemScrollData . scrollTop || 0
}
} ,
async loadServerLibraryItem ( ) {
console . log ( ` Fetching library item " ${ this . libraryItemId } " from server ` )
const libraryItem = await this . $nativeHttp . get ( ` /api/items/ ${ this . libraryItemId } ?expanded=1&include=rssfeed ` , { connectTimeout : 5000 } ) . catch ( ( error ) => {
console . error ( 'Failed' , error )
return null
} )
if ( libraryItem ) {
const localLibraryItem = await this . $db . getLocalLibraryItemByLId ( this . libraryItemId )
if ( localLibraryItem ) {
console . log ( 'Library item has local library item also' , localLibraryItem . id )
libraryItem . localLibraryItem = localLibraryItem
}
this . libraryItem = libraryItem
} else if ( this . $route . query . localLibraryItemId ) {
// Failed to get server library item but is local library item so redirect
return this . $router . replace ( ` /item/ ${ this . $route . query . localLibraryItemId } ?noredirect=1 ` )
} else {
this . $toast . error ( 'Failed to get library item from server' )
return this . $router . replace ( '/bookshelf' )
}
2022-04-24 22:02:22 -04:00
}
2022-03-23 17:59:14 -05:00
} ,
2024-02-04 15:46:26 -06:00
async mounted ( ) {
if ( ! this . libraryItem ) {
await this . loadServerLibraryItem ( )
2023-06-04 15:52:36 -05:00
}
2024-02-04 15:46:26 -06:00
this . init ( )
2022-03-23 17:59:14 -05:00
} ,
beforeDestroy ( ) {
2023-01-12 17:04:47 -06:00
window . removeEventListener ( 'resize' , this . windowResized )
2022-08-25 17:58:29 -05:00
this . $eventBus . $off ( 'library-changed' , this . libraryChanged )
2022-04-07 18:46:58 -05:00
this . $eventBus . $off ( 'new-local-library-item' , this . newLocalLibraryItem )
2022-12-10 10:12:58 -06:00
this . $socket . $off ( 'item_updated' , this . itemUpdated )
2023-06-24 14:45:25 -05:00
this . $socket . $off ( 'rss_feed_open' , this . rssFeedOpen )
this . $socket . $off ( 'rss_feed_closed' , this . rssFeedClosed )
2023-06-04 15:52:36 -05:00
// Set scroll position
if ( window [ 'item-page' ] ) {
this . $store . commit ( 'setLastItemScrollData' , { scrollTop : window [ 'item-page' ] . scrollTop || 0 , id : this . libraryItemId } )
}
2022-03-23 17:59:14 -05:00
}
}
2022-06-02 17:57:37 -05:00
< / script >
< style >
2024-01-01 09:57:51 -06:00
: root {
-- item - page - bg - gradient - height : 100 % ;
}
# item - page - bg - gradient {
transition : opacity 0.5 s ease - in - out ;
height : var ( -- item - page - bg - gradient - height ) ;
}
2022-06-02 17:57:37 -05:00
. title - container {
width : calc ( 100 % - 64 px ) ;
max - width : calc ( 100 % - 64 px ) ;
}
2023-02-09 23:57:59 +01:00
# coverBg > div {
width : 150 vw ! important ;
max - width : 150 vw ! important ;
}
2023-02-26 12:59:44 +01:00
@ media only screen and ( max - width : 500 px ) {
# metadata {
grid - template - columns : auto 1 fr ;
}
}
@ media only screen and ( min - width : 500 px ) {
# metadata {
grid - template - columns : auto 1 fr auto 1 fr ;
}
}
2022-12-08 00:28:28 -05:00
< / style >