mirror of
https://github.com/jellyfin/jellyfin-vue.git
synced 2024-12-04 12:23:29 +00:00
Merge pull request #66 from jellyfin/feat/revampedItemDetails
Feat/revamped item details - Updated and Improved item details page
This commit is contained in:
commit
d8aa022007
@ -60,7 +60,7 @@
|
||||
<user-button v-if="$auth.loggedIn" />
|
||||
</v-app-bar>
|
||||
<v-main>
|
||||
<v-container fluid>
|
||||
<v-container fluid class="pa-0">
|
||||
<nuxt />
|
||||
</v-container>
|
||||
</v-main>
|
||||
|
@ -18,6 +18,7 @@
|
||||
"serverAddress": "Server address",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"playType": "Play {mediaType}",
|
||||
"signIn": "Sign in",
|
||||
"upNext": "Up next",
|
||||
"libraryNotFound": "Library not found",
|
||||
|
@ -1,44 +0,0 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col cols="4">
|
||||
<v-img :src="getImageLink(item.Id, 'primary')"></v-img>
|
||||
</v-col>
|
||||
<v-col cols="8">
|
||||
<h1>{{ item.Name }}</h1>
|
||||
<p>{{ item.Overview }}</p>
|
||||
<v-btn color="primary">{{ $t('play') }}</v-btn>
|
||||
<v-btn>{{ $t('more') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<season-tabs v-if="item.Type === 'Series'" :item="item"></season-tabs>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { BaseItemDto } from '~/api';
|
||||
import imageHelper from '~/mixins/imageHelper';
|
||||
|
||||
export default Vue.extend({
|
||||
mixins: [imageHelper],
|
||||
data() {
|
||||
return {
|
||||
item: {} as BaseItemDto
|
||||
};
|
||||
},
|
||||
|
||||
async beforeMount() {
|
||||
const Item = (
|
||||
await this.$itemsApi.getItems({
|
||||
uId: this.$auth.user.Id,
|
||||
userId: this.$auth.user.Id,
|
||||
ids: this.$route.params.itemId,
|
||||
fields: 'Overview'
|
||||
})
|
||||
).data.Items as BaseItemDto[];
|
||||
|
||||
this.item = Item[0];
|
||||
}
|
||||
});
|
||||
</script>
|
142
pages/item/_itemId/index.vue
Normal file
142
pages/item/_itemId/index.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<v-container fluid class="pa-0 item-container">
|
||||
<v-img
|
||||
v-resize="updateBackdropImage"
|
||||
:src="backdropImageSource"
|
||||
class="d-flex align-end backdrop-image"
|
||||
max-width="100%"
|
||||
>
|
||||
<div class="d-flex align-end gradient-container">
|
||||
<div class="d-flex flex-wrap item-details-container">
|
||||
<div class="itemDetailsLeft">
|
||||
<v-img
|
||||
v-if="
|
||||
item.ImageTags && item.ImageTags.Logo && getAspectRatio() > 1
|
||||
"
|
||||
:src="getImageLink(item.Id, 'Logo')"
|
||||
contain
|
||||
:alt="item.Name"
|
||||
max-width="50%"
|
||||
class="mb-4"
|
||||
></v-img>
|
||||
<h1 v-else>{{ item.Name }}</h1>
|
||||
<div class="item-sub-heading">{{ renderItemSubHeading() }}</div>
|
||||
<p class="item-overview">{{ item.Overview }}</p>
|
||||
</div>
|
||||
<div class="item-details-right">
|
||||
<v-btn
|
||||
class="play-button"
|
||||
color="primary"
|
||||
:to="`./${item.Id}/play`"
|
||||
>{{ $t('playType', { mediaType: item.Type }) }}</v-btn
|
||||
>
|
||||
<v-btn>{{ $t('more') }}</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-img>
|
||||
<season-tabs v-if="item.Type === 'Series'" :item="item"></season-tabs>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { BaseItemDto } from '~/api';
|
||||
import imageHelper from '~/mixins/imageHelper';
|
||||
|
||||
export default Vue.extend({
|
||||
mixins: [imageHelper],
|
||||
data() {
|
||||
return {
|
||||
item: {} as BaseItemDto,
|
||||
backdropImageSource: ''
|
||||
};
|
||||
},
|
||||
|
||||
async beforeMount() {
|
||||
const Item = (
|
||||
await this.$itemsApi.getItems({
|
||||
uId: this.$auth.user.Id,
|
||||
userId: this.$auth.user.Id,
|
||||
ids: this.$route.params.itemId,
|
||||
fields: 'Overview,Genres'
|
||||
})
|
||||
).data.Items as BaseItemDto[];
|
||||
|
||||
this.item = Item[0];
|
||||
|
||||
this.updateBackdropImage();
|
||||
},
|
||||
methods: {
|
||||
getAspectRatio() {
|
||||
return window.innerWidth / window.innerHeight;
|
||||
},
|
||||
getItemBackdrop(id: string): string {
|
||||
if (window.innerWidth < window.innerHeight) {
|
||||
return `${this.$axios.defaults.baseURL}/Items/${id}/Images/Primary`;
|
||||
} else {
|
||||
return `${this.$axios.defaults.baseURL}/Items/${id}/Images/Backdrop`;
|
||||
}
|
||||
},
|
||||
ticksToTime(ticks: number) {
|
||||
const ms = ticks / 600000000;
|
||||
if (Math.floor(ms / 60)) {
|
||||
return `${Math.floor(ms / 60)} hrs ${Math.floor(ms % 60)} min`;
|
||||
} else {
|
||||
return `${Math.floor(ms % 60)} min`;
|
||||
}
|
||||
},
|
||||
renderItemSubHeading() {
|
||||
const response = [];
|
||||
if (this.item.Genres) {
|
||||
response.push(this.item.Genres[0]);
|
||||
}
|
||||
if (this.item.RunTimeTicks) {
|
||||
response.push(this.ticksToTime(this.item.RunTimeTicks));
|
||||
}
|
||||
if (this.item.ProductionYear) {
|
||||
response.push(this.item.ProductionYear);
|
||||
}
|
||||
return response.join(' • ');
|
||||
},
|
||||
updateBackdropImage() {
|
||||
this.backdropImageSource = this.getItemBackdrop(this.item.Id || '');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.item-container {
|
||||
margin: auto;
|
||||
max-width: calc(85vh * 16 / 9);
|
||||
}
|
||||
|
||||
.backdrop-image {
|
||||
max-width: 95em;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.item-details-container {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.gradient-container {
|
||||
background: linear-gradient(0deg, #0c0c0c, transparent);
|
||||
height: 30vh;
|
||||
}
|
||||
|
||||
.item-sub-heading {
|
||||
font-size: 0.8rem;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 30em) {
|
||||
.item-overview {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 90vw;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue
Block a user