Compare commits
No commits in common. '1cd1584bb3efd4d5d74a020205ef4b8a902f41ad' and '8e2300bf533b8c2c9a6791c30825856ce08028d7' have entirely different histories.
1cd1584bb3
...
8e2300bf53
@ -0,0 +1,52 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import MediaScroller from './MediaScroller.vue'
|
||||||
|
import { ContinueWatching } from '@/feed'
|
||||||
|
import type { FeedItem } from '@/feed'
|
||||||
|
|
||||||
|
const Items = (): FeedItem[] => {
|
||||||
|
// const feed = []
|
||||||
|
// jfapi
|
||||||
|
// .GetNextUp('http://192.168.0.63:30013')
|
||||||
|
// .then((data) => {
|
||||||
|
// for (const item of data.Items) {
|
||||||
|
// const thumbtag = item.ImageTags.Thumb ? item.ImageTags.Thumb : ''
|
||||||
|
// let summary = ''
|
||||||
|
// jfapi.GetItem('http://192.168.0.63:30013', '', item.Id).then((item) => {
|
||||||
|
// summary = item.Overview !== undefined ? item.Overview : ''
|
||||||
|
// })
|
||||||
|
// feed.push({
|
||||||
|
// title: item.SeriesName,
|
||||||
|
// subtext: `S${item.ParentIndexNumber}:E${item.IndexNumber} - ${item.Name}`,
|
||||||
|
// image: jfapi.ThumbImageUrl('http://192.168.0.63:30013', item.Id, thumbtag),
|
||||||
|
// summary: summary,
|
||||||
|
// infoSubtext: `S${item.ParentIndexNumber}:E${item.IndexNumber} - ${item.Name}`,
|
||||||
|
// imdbRating: item.CommunityRating,
|
||||||
|
// runtime: getDisplayDuration(item.RunTimeTicks),
|
||||||
|
// date: item.ProductionYear,
|
||||||
|
// MPAA: item.OfficialRating,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// return feed
|
||||||
|
// })
|
||||||
|
// .catch((err) => {
|
||||||
|
// return err
|
||||||
|
// })
|
||||||
|
return ContinueWatching
|
||||||
|
}
|
||||||
|
|
||||||
|
// function getDisplayDuration(ticks: number) {
|
||||||
|
// const totalMinutes = Math.round(ticks / 600000000) || 1
|
||||||
|
// const totalHours = Math.floor(totalMinutes / 60)
|
||||||
|
// const remainderMinutes = totalMinutes % 60
|
||||||
|
// const result = []
|
||||||
|
// if (totalHours > 0) {
|
||||||
|
// result.push(`${totalHours}h`)
|
||||||
|
// }
|
||||||
|
// result.push(`${remainderMinutes}m`)
|
||||||
|
// return result.join(' ')
|
||||||
|
// }
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<MediaScroller title="Continue Watching" landscape :items="Items()" />
|
||||||
|
</template>
|
||||||
@ -1,27 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { View } from '@/jfapi'
|
|
||||||
import NextUp from './NextUp.vue'
|
|
||||||
import LatestMedia from './LatestMedia.vue'
|
|
||||||
import { onBeforeMount, ref } from 'vue'
|
|
||||||
|
|
||||||
// const props = defineProps<{
|
|
||||||
// views: View[]
|
|
||||||
// }>()
|
|
||||||
|
|
||||||
const views = ref<View[]>()
|
|
||||||
const viewstring = ref<string | null>('')
|
|
||||||
views.value = undefined
|
|
||||||
onBeforeMount(() => {
|
|
||||||
viewstring.value = localStorage.getItem('jf_views')
|
|
||||||
views.value = JSON.parse(viewstring.value ? viewstring.value : '')
|
|
||||||
})
|
|
||||||
console.log('HomeMedia =>', views.value)
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<NextUp />
|
|
||||||
<!-- <MediaScroller title="Latest Movies" :items="LatestMovies" />
|
|
||||||
<MediaScroller title="Latest Anime" :items="LatestAnime" /> -->
|
|
||||||
<LatestMedia v-for="view of views" :view="view" :key="view.Id" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import jfapi from '@/jfapi'
|
|
||||||
import MediaScroller from './MediaScroller.vue'
|
|
||||||
import type { FeedItem } from '@/feed'
|
|
||||||
import type { View } from '@/jfapi'
|
|
||||||
import { getDisplayDuration, getImageLink } from './utils'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
view: View
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const Items = async (): Promise<FeedItem[]> => {
|
|
||||||
return new Promise(async (resolve) => {
|
|
||||||
const feed: FeedItem[] = []
|
|
||||||
const userid = localStorage.getItem('jf_userid')
|
|
||||||
const data = await jfapi.GetLatest(userid ? userid : '', props.view)
|
|
||||||
if (data !== null) {
|
|
||||||
console.log(data)
|
|
||||||
for (const item of data) {
|
|
||||||
const summary = ''
|
|
||||||
if (userid === null) {
|
|
||||||
window.location.href = '/login'
|
|
||||||
}
|
|
||||||
// const fullitem = await jfapi.GetItem(userid, item.Id)
|
|
||||||
// if (fullitem === null) {
|
|
||||||
// continue
|
|
||||||
// } else {
|
|
||||||
// summary = fullitem.Overview
|
|
||||||
// }
|
|
||||||
feed.push({
|
|
||||||
title: item.SeriesName || item.Name,
|
|
||||||
subtext: item.SeriesName
|
|
||||||
? `S${item.ParentIndexNumber}:E${item.IndexNumber} - ${item.Name}`
|
|
||||||
: String(item.ProductionYear),
|
|
||||||
image: getImageLink(item),
|
|
||||||
summary: summary,
|
|
||||||
// infoSubtext: item.SeriesName
|
|
||||||
// ? `S${item.ParentIndexNumber}:E${item.IndexNumber} - ${item.Name}`
|
|
||||||
// : fullitem.OriginalTitle != item.Name
|
|
||||||
// ? fullitem.OriginalTitle
|
|
||||||
// : '',
|
|
||||||
infoSubtext: '',
|
|
||||||
imdbRating: String(item.CommunityRating),
|
|
||||||
runtime: getDisplayDuration(item.RunTimeTicks),
|
|
||||||
date: String(item.ProductionYear),
|
|
||||||
MPAA: item.OfficialRating,
|
|
||||||
tag:
|
|
||||||
item.UserData?.UnplayedItemCount !== undefined
|
|
||||||
? String(item.UserData.UnplayedItemCount)
|
|
||||||
: '',
|
|
||||||
itemId: item.Id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
console.log('LatestMedia.vue =>', feed)
|
|
||||||
resolve(feed)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<MediaScroller :title="`Latest ${view?.Name}`" :feed-items="Items()" />
|
|
||||||
</template>
|
|
||||||
@ -1,110 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { FeedItem } from '@/feed'
|
|
||||||
import jfapi, { type View } from '@/jfapi'
|
|
||||||
import { getImageLink, getDisplayDuration } from './utils'
|
|
||||||
import { onBeforeMount, ref } from 'vue'
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
import ScrollerItemPortrait from './ScrollerItemPortrait.vue'
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const viewId = String(route.params.id)
|
|
||||||
|
|
||||||
console.log(viewId)
|
|
||||||
|
|
||||||
const Items = async (viewId: string, type: string): Promise<FeedItem[]> => {
|
|
||||||
return new Promise(async (resolve) => {
|
|
||||||
const feed: FeedItem[] = []
|
|
||||||
const userid = localStorage.getItem('jf_userid')
|
|
||||||
const data = await jfapi.GetItemsInView(userid ? userid : '', viewId, type)
|
|
||||||
if (data !== null) {
|
|
||||||
console.log(data)
|
|
||||||
for (const item of data.Items) {
|
|
||||||
const summary = ''
|
|
||||||
if (userid === null) {
|
|
||||||
window.location.href = '/login'
|
|
||||||
}
|
|
||||||
feed.push({
|
|
||||||
title: item.SeriesName || item.Name,
|
|
||||||
subtext: item.SeriesName
|
|
||||||
? `S${item.ParentIndexNumber}:E${item.IndexNumber} - ${item.Name}`
|
|
||||||
: String(item.ProductionYear),
|
|
||||||
image: getImageLink(item),
|
|
||||||
summary: summary,
|
|
||||||
infoSubtext: '',
|
|
||||||
imdbRating: String(item.CommunityRating),
|
|
||||||
runtime: getDisplayDuration(item.RunTimeTicks),
|
|
||||||
date: String(item.ProductionYear),
|
|
||||||
MPAA: item.OfficialRating,
|
|
||||||
tag:
|
|
||||||
item.UserData?.UnplayedItemCount !== undefined
|
|
||||||
? String(item.UserData.UnplayedItemCount)
|
|
||||||
: '',
|
|
||||||
itemId: item.Id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
console.log('MediaGrid.vue =>', feed)
|
|
||||||
resolve(feed)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const items = ref<FeedItem[]>()
|
|
||||||
const views = ref<View[]>()
|
|
||||||
const viewstring = ref<string | null>('')
|
|
||||||
views.value = undefined
|
|
||||||
onBeforeMount(async () => {
|
|
||||||
viewstring.value = localStorage.getItem('jf_views')
|
|
||||||
views.value = JSON.parse(viewstring.value ? viewstring.value : '')
|
|
||||||
let viewType = ''
|
|
||||||
for (const view of views.value) {
|
|
||||||
if (view.Id === viewId) {
|
|
||||||
if (view.CollectionType.toLowerCase() === 'movies') {
|
|
||||||
viewType = 'Movie'
|
|
||||||
} else if (view.CollectionType.toLowerCase() === 'tvshows') {
|
|
||||||
viewType = 'Series'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log('ItemType =>', viewType)
|
|
||||||
const data = ref<FeedItem[]>()
|
|
||||||
console.log(viewType)
|
|
||||||
data.value = await Items(viewId, viewType)
|
|
||||||
items.value = data.value
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="media-grid">
|
|
||||||
<div class="grid-wrapper">
|
|
||||||
<ScrollerItemPortrait
|
|
||||||
v-for="item in items"
|
|
||||||
:title="item.title"
|
|
||||||
:subtext="item.subtext"
|
|
||||||
:image="item.image"
|
|
||||||
:info-subtext="item.infoSubtext"
|
|
||||||
:date="item.date"
|
|
||||||
:runtime="item.runtime"
|
|
||||||
:summary="item.summary"
|
|
||||||
:-m-p-a-a="item.MPAA"
|
|
||||||
:imdb-rating="item.imdbRating"
|
|
||||||
:key="item.title"
|
|
||||||
:itemId="item.itemId"
|
|
||||||
:tag="item.tag"
|
|
||||||
class="scroller-item"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.media-grid {
|
|
||||||
width: 80%;
|
|
||||||
margin: auto;
|
|
||||||
margin-top: 3em;
|
|
||||||
}
|
|
||||||
.grid-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="info-misc">
|
|
||||||
<div class="misc-info-year">2019</div>
|
|
||||||
<div class="misc-info-runtime">2h 12m</div>
|
|
||||||
<div class="misc-info imdb-rating">
|
|
||||||
<font-awesome-icon icon="fa-solid fa-star" size="xs" class="star-rating" /> 7.6
|
|
||||||
</div>
|
|
||||||
<div class="misc-info-age-rating"><span class="age-rating">R</span></div>
|
|
||||||
<div class="media-showcase-end-time">Ends at 6:23pm</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.info-misc {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
gap: 3em;
|
|
||||||
color: var(--color-text);
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import jfapi from '@/jfapi'
|
|
||||||
import MediaScroller from './MediaScroller.vue'
|
|
||||||
import type { FeedItem } from '@/feed'
|
|
||||||
import type { Item } from '@/jfapi'
|
|
||||||
|
|
||||||
const Items = async (): Promise<FeedItem[]> => {
|
|
||||||
return new Promise(async (resolve) => {
|
|
||||||
const feed: FeedItem[] = []
|
|
||||||
const data = await jfapi.GetNextUp()
|
|
||||||
if (data !== null) {
|
|
||||||
for (const item of data.Items) {
|
|
||||||
const summary = ''
|
|
||||||
const userid = localStorage.getItem('jf_userid')
|
|
||||||
if (userid === null) {
|
|
||||||
window.location.href = '/login'
|
|
||||||
}
|
|
||||||
// const fullitem = await jfapi.GetItem(userid ? userid : '', item.Id)
|
|
||||||
// if (fullitem === null) {
|
|
||||||
// summary = 'Failed to fetch content.'
|
|
||||||
// } else {
|
|
||||||
// summary = fullitem.Overview
|
|
||||||
// }
|
|
||||||
feed.push({
|
|
||||||
title: item.SeriesName !== undefined ? item.SeriesName : '',
|
|
||||||
subtext: `S${item.ParentIndexNumber}:E${item.IndexNumber} - ${item.Name}`,
|
|
||||||
image: getImageLink(item),
|
|
||||||
summary: summary,
|
|
||||||
infoSubtext: `S${item.ParentIndexNumber}:E${item.IndexNumber} - ${item.Name}`,
|
|
||||||
imdbRating: String(item.CommunityRating),
|
|
||||||
runtime: getDisplayDuration(item.RunTimeTicks),
|
|
||||||
date: String(item.ProductionYear),
|
|
||||||
MPAA: item.OfficialRating,
|
|
||||||
itemId: item.Id,
|
|
||||||
tag: '',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
console.log('NextUp.vue =>', feed)
|
|
||||||
resolve(feed)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImageLink(item: Item): string {
|
|
||||||
if (item.ParentThumbItemId === undefined) {
|
|
||||||
// using backdrop
|
|
||||||
return jfapi.BackdropImageUrl(
|
|
||||||
item.ParentBackdropItemId ? item.ParentBackdropItemId : '',
|
|
||||||
item.ParentBackdropImageTags ? item.ParentBackdropImageTags[0] : '',
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// using thumb
|
|
||||||
return jfapi.ThumbImageUrl(
|
|
||||||
item.ParentThumbItemId,
|
|
||||||
item.ParentThumbImageTag ? item.ParentThumbImageTag : '',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDisplayDuration(ticks: number) {
|
|
||||||
const totalMinutes = Math.round(ticks / 600000000) || 1
|
|
||||||
const totalHours = Math.floor(totalMinutes / 60)
|
|
||||||
const remainderMinutes = totalMinutes % 60
|
|
||||||
const result = []
|
|
||||||
if (totalHours > 0) {
|
|
||||||
result.push(`${totalHours}h`)
|
|
||||||
}
|
|
||||||
result.push(`${remainderMinutes}m`)
|
|
||||||
return result.join(' ')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<MediaScroller title="Next Up" landscape :feed-items="Items()" />
|
|
||||||
</template>
|
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<div class="item">
|
||||||
|
<i>
|
||||||
|
<slot name="icon"></slot>
|
||||||
|
</i>
|
||||||
|
<div class="details">
|
||||||
|
<h3>
|
||||||
|
<slot name="heading"></slot>
|
||||||
|
</h3>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.item {
|
||||||
|
margin-top: 2rem;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
place-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.4rem;
|
||||||
|
color: var(--color-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.item {
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
top: calc(50% - 25px);
|
||||||
|
left: -26px;
|
||||||
|
position: absolute;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:before {
|
||||||
|
content: ' ';
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: calc(50% + 25px);
|
||||||
|
height: calc(50% - 25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:after {
|
||||||
|
content: ' ';
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: calc(50% + 25px);
|
||||||
|
height: calc(50% - 25px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:first-of-type:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:last-of-type:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import jfapi from '@/jfapi'
|
|
||||||
import type { Item } from '@/jfapi'
|
|
||||||
|
|
||||||
function getImageLink(item: Item): string {
|
|
||||||
if (item.SeriesId === undefined) {
|
|
||||||
// using movie primary
|
|
||||||
return jfapi.PrimaryImageUrl(item.Id, item.ImageTags.Primary ? item.ImageTags.Primary : '')
|
|
||||||
} else {
|
|
||||||
// using series primary
|
|
||||||
return jfapi.PrimaryImageUrl(
|
|
||||||
item.SeriesId,
|
|
||||||
item.SeriesPrimaryImageTag ? item.SeriesPrimaryImageTag[0] : '',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDisplayDuration(ticks: number) {
|
|
||||||
const totalMinutes = Math.round(ticks / 600000000) || 1
|
|
||||||
const totalHours = Math.floor(totalMinutes / 60)
|
|
||||||
const remainderMinutes = totalMinutes % 60
|
|
||||||
const result = []
|
|
||||||
if (totalHours > 0) {
|
|
||||||
result.push(`${totalHours}h`)
|
|
||||||
}
|
|
||||||
result.push(`${remainderMinutes}m`)
|
|
||||||
return result.join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
export { getImageLink, getDisplayDuration }
|
|
||||||
@ -1,22 +1,29 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import type { User, View } from '@/jfapi'
|
|
||||||
|
type UserData = {
|
||||||
|
host?: string
|
||||||
|
id?: string
|
||||||
|
name?: string
|
||||||
|
views?: [View]
|
||||||
|
}
|
||||||
|
type View = {
|
||||||
|
name: string
|
||||||
|
mediaType: string
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
export const useDataStore = defineStore('data', () => {
|
export const useDataStore = defineStore('data', () => {
|
||||||
const data = ref<User>({
|
const data = ref<UserData>({})
|
||||||
Name: '',
|
function setServerHost(host: string) {
|
||||||
Host: '',
|
data.value.host = host
|
||||||
Id: '',
|
}
|
||||||
})
|
function setViews(views: [View]) {
|
||||||
// function setServerHost(host: string) {
|
data.value.views = views
|
||||||
// data.value.Host = host
|
}
|
||||||
// }
|
function setUser(id: string, name: string) {
|
||||||
function setViews(views: View[]) {
|
data.value.id = id
|
||||||
data.value.Views = views
|
data.value.name = name
|
||||||
}
|
}
|
||||||
// function setUser(id: string, name: string) {
|
return { data, setServerHost, setViews, setUser }
|
||||||
// data.value.Id = id
|
|
||||||
// data.value.Name = name
|
|
||||||
// }
|
|
||||||
return { setViews }
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,89 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import MediaShowcase from '@/components/MediaShowcase.vue'
|
|
||||||
import type { View, ViewList } from '@/jfapi'
|
|
||||||
import { onBeforeMount, ref } from 'vue'
|
|
||||||
import jfapi from '@/jfapi'
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
|
|
||||||
const views = ref<View[]>()
|
|
||||||
onBeforeMount(async () => {
|
|
||||||
const viewlist = ref<ViewList | null>()
|
|
||||||
const userid = localStorage.getItem('jf_userid')
|
|
||||||
const host = localStorage.getItem('jf_host')
|
|
||||||
console.log('Host =>', host)
|
|
||||||
viewlist.value = await jfapi.LoadViews(userid ? userid : '')
|
|
||||||
if (window.location.pathname.toLowerCase() !== '/login') {
|
|
||||||
if (host === null) {
|
|
||||||
window.location.href = '/login'
|
|
||||||
} else if (viewlist.value === null) {
|
|
||||||
window.location.href = '/login'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
localStorage.setItem('jf_views', JSON.stringify(viewlist.value ? viewlist.value.Items : ''))
|
|
||||||
views.value = viewlist.value ? viewlist.value.Items : undefined
|
|
||||||
console.log(views.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const homeIsActive = () => {
|
|
||||||
return route.path === '/home'
|
|
||||||
}
|
|
||||||
const viewIsActive = (id: string) => {
|
|
||||||
return route.path.split('/')[2] === id
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<main>
|
|
||||||
<MediaShowcase />
|
|
||||||
<div class="home-nav">
|
|
||||||
<RouterLink to="/home" class="home-nav-item" :class="homeIsActive() ? 'active' : ''"
|
|
||||||
>Home</RouterLink
|
|
||||||
>
|
|
||||||
<RouterLink
|
|
||||||
class="home-nav-item"
|
|
||||||
v-for="view in views"
|
|
||||||
:to="`/view/${view.Id}`"
|
|
||||||
:key="view.Id"
|
|
||||||
:class="viewIsActive(view.Id) ? 'active' : ''"
|
|
||||||
>{{ view.Name }}</RouterLink
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="media">
|
|
||||||
<RouterView v-slot="{ Component, route }"
|
|
||||||
><component :is="Component" :key="route.path"
|
|
||||||
/></RouterView>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
a {
|
|
||||||
color: var(--color-text);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.home-nav {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 1.5em;
|
|
||||||
gap: 3em;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.home-nav-item:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--color-text-faded);
|
|
||||||
}
|
|
||||||
.home-nav-item.active {
|
|
||||||
padding: 0.4em 1.5em;
|
|
||||||
background-color: var(--color-text);
|
|
||||||
border-radius: 15px;
|
|
||||||
color: var(--color-text-dark);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.home-nav-item.active:hover {
|
|
||||||
background-color: var(--color-text-faded);
|
|
||||||
}
|
|
||||||
.media {
|
|
||||||
margin: 0 10%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,233 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import MediaInfo from '@/components/MediaInfo.vue'
|
|
||||||
import jfapi, { type Item } from '@/jfapi'
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const itemId = String(route.params.id)
|
|
||||||
|
|
||||||
const item = ref<Item>()
|
|
||||||
const directors = ref<string[]>([])
|
|
||||||
const writers = ref<string[]>([])
|
|
||||||
// const studios = ref<string[]>()
|
|
||||||
const numAudioStreams = ref(0)
|
|
||||||
const userid = localStorage.getItem('jf_userid')
|
|
||||||
jfapi.GetItem(userid ? userid : '', itemId).then((data) => {
|
|
||||||
if (data === null) {
|
|
||||||
// error
|
|
||||||
} else {
|
|
||||||
item.value = data
|
|
||||||
}
|
|
||||||
if (item.value !== undefined) {
|
|
||||||
for (const stream of item.value.MediaStreams) {
|
|
||||||
if (stream.Type.toLowerCase() === 'audio') {
|
|
||||||
numAudioStreams.value++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const person of item.value.People) {
|
|
||||||
if (person.Type.toLowerCase() === 'director') {
|
|
||||||
directors.value.push(person.Name)
|
|
||||||
} else if (person.Type.toLowerCase() === 'writer') {
|
|
||||||
writers.value.push(person.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(writers.value, directors.value)
|
|
||||||
})
|
|
||||||
const arrayRange = (start: number, stop: number) =>
|
|
||||||
Array.from({ length: (stop - start) / 1 + 1 }, (value, index) => start + index * 1)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="container"
|
|
||||||
:style="{
|
|
||||||
backgroundAttachment: 'fixed',
|
|
||||||
backgroundSize: 'cover',
|
|
||||||
backgroundImage: `url(${jfapi.FullBackdropImageUrl(item ? item.Id : '', item ? item.BackdropImageTags[0] : '', 0)})`,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="content-wrapper">
|
|
||||||
<div class="item-info">
|
|
||||||
<div class="logo">
|
|
||||||
<img :src="`${jfapi.LogoImageUrl(item.Id, item.ImageTags.Logo)}`" alt="" />
|
|
||||||
<MediaInfo />
|
|
||||||
</div>
|
|
||||||
<div class="info-wrapper">
|
|
||||||
<div class="section">
|
|
||||||
<div class="detail-buttons">
|
|
||||||
<div class="play-button">
|
|
||||||
<button class="primary-button">
|
|
||||||
<font-awesome-icon icon="fa-solid fa-play" size="md" />Play Now
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="user-buttons">
|
|
||||||
<div class="watched">
|
|
||||||
<font-awesome-icon icon="fa-solid fa-check" size="xl" class="clickable" />
|
|
||||||
</div>
|
|
||||||
<div class="favorited">
|
|
||||||
<font-awesome-icon :icon="['far', 'heart']" size="xl" class="clickable" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="media-streams">
|
|
||||||
<div class="stream-type">Video</div>
|
|
||||||
<div class="stream-value">{{ item?.MediaStreams[0].DisplayTitle }}</div>
|
|
||||||
<div class="stream-type">Audio</div>
|
|
||||||
<div class="stream-selector" v-if="numAudioStreams > 1">
|
|
||||||
<select name="audio-stream-select" :id="`${item?.Id}-audio-select`">
|
|
||||||
<option
|
|
||||||
:value="`${item?.MediaStreams}`"
|
|
||||||
v-for="i in arrayRange(1, numAudioStreams)"
|
|
||||||
:key="`audio-stream-${i}`"
|
|
||||||
>
|
|
||||||
{{ item?.MediaStreams[i].DisplayTitle }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="stream-value" v-else>{{ item?.MediaStreams[1].DisplayTitle }}</div>
|
|
||||||
<div class="stream-type">Subtitles</div>
|
|
||||||
<div class="stream-selector">
|
|
||||||
<select
|
|
||||||
name="subtitle-stream-select"
|
|
||||||
:id="`${item?.Id}-subtitle-select`"
|
|
||||||
v-if="item?.MediaStreams.length > numAudioStreams + 1"
|
|
||||||
>
|
|
||||||
<option value="Off">Off</option>
|
|
||||||
<option
|
|
||||||
:value="`${item?.MediaStreams}`"
|
|
||||||
v-for="i in arrayRange(numAudioStreams + 1, item.MediaStreams.length - 1)"
|
|
||||||
:key="`subtitle-stream-${i}`"
|
|
||||||
>
|
|
||||||
{{ item?.MediaStreams[i].DisplayTitle }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<div class="stream-value" v-else>None</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<blockquote class="tagline" v-if="item?.Taglines.length > 0">
|
|
||||||
{{ item?.Taglines[0] }}
|
|
||||||
</blockquote>
|
|
||||||
<div class="overview">
|
|
||||||
{{ item?.Overview }}
|
|
||||||
</div>
|
|
||||||
<div class="misc-info">
|
|
||||||
<div class="info-type">Genre</div>
|
|
||||||
<div class="info-value">{{ item?.Genres.join(', ') }}</div>
|
|
||||||
<div class="info-type">{{ 'Director' + (directors.length > 1 ? 's' : '') }}</div>
|
|
||||||
<div class="info-value">{{ directors.join(', ') }}</div>
|
|
||||||
<div class="info-type">{{ 'Writer' + (writers.length > 1 ? 's' : '') }}</div>
|
|
||||||
<div class="info-value">{{ writers.join(', ') }}</div>
|
|
||||||
<div class="info-type">{{ 'Studio' + (item?.Studios.length > 1 ? 's' : '') }}</div>
|
|
||||||
<div class="info-value">{{ item?.Studios.map((s) => s.Name).join(', ') }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.container {
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
.content-wrapper {
|
|
||||||
width: 95%;
|
|
||||||
margin: auto;
|
|
||||||
padding-top: 3em;
|
|
||||||
}
|
|
||||||
.item-info {
|
|
||||||
padding: 3em 5em;
|
|
||||||
width: 900px;
|
|
||||||
min-height: 80vh;
|
|
||||||
background: rgba(0, 0, 0, 0.4);
|
|
||||||
color: rgba(255, 255, 255, 0.5);
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
-webkit-backdrop-filter: blur(4px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
.info-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: baseline;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 3em;
|
|
||||||
width: 95%;
|
|
||||||
margin: 0 auto;
|
|
||||||
margin-top: 3em;
|
|
||||||
}
|
|
||||||
.logo {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.logo img {
|
|
||||||
max-height: 230px;
|
|
||||||
}
|
|
||||||
.section {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.detail-buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1em;
|
|
||||||
margin-left: 2em;
|
|
||||||
}
|
|
||||||
.user-buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 2em;
|
|
||||||
}
|
|
||||||
.media-streams {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 0.5fr 4fr;
|
|
||||||
}
|
|
||||||
.stream-type {
|
|
||||||
text-align: right;
|
|
||||||
padding: 0.2em;
|
|
||||||
margin-right: 0.3em;
|
|
||||||
color: var(--color-text-faded);
|
|
||||||
}
|
|
||||||
.stream-value {
|
|
||||||
padding: 0.2em;
|
|
||||||
}
|
|
||||||
.stream-selector {
|
|
||||||
margin: 0.2em;
|
|
||||||
}
|
|
||||||
.stream-selector select {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.misc-info {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 0.5fr 4fr;
|
|
||||||
}
|
|
||||||
.misc-info .info-type {
|
|
||||||
text-align: right;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
color: var(--color-text-faded);
|
|
||||||
}
|
|
||||||
.tagline {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.tagline::before {
|
|
||||||
content: '\201C';
|
|
||||||
font-size: 156px;
|
|
||||||
position: absolute;
|
|
||||||
top: -80px;
|
|
||||||
left: -20px;
|
|
||||||
color: rgba(0, 0, 0, 0.4);
|
|
||||||
z-index: -1;
|
|
||||||
font-family: 'Georgia', Times, serif;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Loading…
Reference in new issue