Proof of concept tmdb data fetching running on tizen
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* @deprecated - Check @/utils/playback-profiles/index
|
||||
*/
|
||||
|
||||
import { isApple, isTizen, isTv, isWebOS } from '$lib/utils/browser-detection';
|
||||
import { isApple, isTizen, isTv, isWebOS } from '../../../../utils/browser-detection';
|
||||
|
||||
/**
|
||||
* Determines if audio codec is supported
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
isWebOS,
|
||||
isXbox,
|
||||
safariVersion
|
||||
} from '$lib/utils/browser-detection';
|
||||
} from '../../../../utils/browser-detection';
|
||||
|
||||
/**
|
||||
* Gets the max video bitrate
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
hasEac3Support,
|
||||
hasMp3AudioSupport
|
||||
} from './mp4-audio-formats';
|
||||
import { isEdge } from '$lib/utils/browser-detection';
|
||||
import { isEdge } from '../../../../utils/browser-detection';
|
||||
|
||||
/**
|
||||
* Gets an array with the supported fmp4 codecs
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
isFirefox,
|
||||
isTizen,
|
||||
isWebOS
|
||||
} from '$lib/utils/browser-detection';
|
||||
} from '../../../../utils/browser-detection';
|
||||
|
||||
/**
|
||||
* Gets an array of supported fmp4 video codecs
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { hasH264Support, hasH265Support } from './mp4-video-formats';
|
||||
import { hasEac3Support, hasAacSupport } from './mp4-audio-formats';
|
||||
import { getSupportedAudioCodecs } from './audio-formats';
|
||||
import { isTv } from '$lib/utils/browser-detection';
|
||||
import { isTv } from '../../../../utils/browser-detection';
|
||||
|
||||
/**
|
||||
* Check if client supports AC3 in HLS stream
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
isTizen55,
|
||||
isTv,
|
||||
isWebOS
|
||||
} from '$lib/utils/browser-detection';
|
||||
} from '../../../../utils/browser-detection';
|
||||
|
||||
/**
|
||||
* Checks if the client can play the AC3 codec
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @deprecated - Check @/utils/playback-profiles/index
|
||||
*/
|
||||
|
||||
import { isApple, isTizen, isTizen55, isTv, isWebOS5 } from '$lib/utils/browser-detection';
|
||||
import { isApple, isTizen, isTizen55, isTv, isWebOS5 } from '../../../../utils/browser-detection';
|
||||
|
||||
/**
|
||||
* Checks if the client has support for the H264 codec
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @deprecated - Check @/utils/playback-profiles/index
|
||||
*/
|
||||
|
||||
import { isEdge, isTizen, isTv, supportsMediaSource } from '$lib/utils/browser-detection';
|
||||
import { isEdge, isTizen, isTv, supportsMediaSource } from '../../../../utils/browser-detection';
|
||||
|
||||
/**
|
||||
* Checks if the client can play native HLS
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @deprecated - Check @/utils/playback-profiles/index
|
||||
*/
|
||||
|
||||
import { isWebOS } from '$lib/utils/browser-detection';
|
||||
import { isWebOS } from '../../../../utils/browser-detection';
|
||||
|
||||
/**
|
||||
* Get an array of supported codecs
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
isChromiumBased,
|
||||
isAndroid,
|
||||
isTizen
|
||||
} from '$lib/utils/browser-detection';
|
||||
} from '../../../utils/browser-detection';
|
||||
|
||||
/**
|
||||
* Returns a valid TranscodingProfile for the current platform.
|
||||
|
||||
23
src/lib/components-new/Card/CardPlaceholder.svelte
Normal file
23
src/lib/components-new/Card/CardPlaceholder.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import classNames from 'classnames';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
export let index = 0;
|
||||
export let size: 'dynamic' | 'md' | 'lg' = 'md';
|
||||
export let orientation: 'portrait' | 'landscape' = 'landscape';
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={classNames('rounded-xl overflow-hidden shadow-lg placeholder shrink-0', {
|
||||
'aspect-video': orientation === 'landscape',
|
||||
'aspect-[2/3]': orientation === 'portrait',
|
||||
'w-44': size === 'md' && orientation === 'portrait',
|
||||
'h-44': size === 'md' && orientation === 'landscape',
|
||||
'w-60': size === 'lg' && orientation === 'portrait',
|
||||
'h-60': size === 'lg' && orientation === 'landscape',
|
||||
'w-full': size === 'dynamic'
|
||||
})}
|
||||
style={'animation-delay: ' + ((index * 100) % 2000) + 'ms;'}
|
||||
transition:fade|global
|
||||
tabindex="0"
|
||||
/>
|
||||
75
src/lib/components-new/Carousel/Carousel.svelte
Normal file
75
src/lib/components-new/Carousel/Carousel.svelte
Normal file
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import IconButton from '../IconButton.svelte';
|
||||
import { ChevronLeft, ChevronRight } from 'radix-icons-svelte';
|
||||
import classNames from 'classnames';
|
||||
import Container from '../../../Container.svelte';
|
||||
|
||||
export let gradientFromColor = 'from-stone-950';
|
||||
export let heading = '';
|
||||
|
||||
let carousel: HTMLDivElement | undefined;
|
||||
let scrollX = 0;
|
||||
export let scrollClass = '';
|
||||
</script>
|
||||
|
||||
<div class={classNames('flex flex-col gap-4 group/carousel', $$restProps.class)}>
|
||||
<div class={'flex justify-between items-center gap-4 ' + scrollClass}>
|
||||
<slot name="title">
|
||||
<div class="font-semibold text-xl">{heading}</div>
|
||||
</slot>
|
||||
<div
|
||||
class={classNames(
|
||||
'flex gap-2 sm:opacity-0 transition-opacity sm:group-hover/carousel:opacity-100',
|
||||
{
|
||||
hidden: (carousel?.scrollWidth || 0) === (carousel?.clientWidth || 0)
|
||||
}
|
||||
)}
|
||||
>
|
||||
<IconButton
|
||||
on:click={() => {
|
||||
carousel?.scrollTo({ left: scrollX - carousel?.clientWidth * 0.8, behavior: 'smooth' });
|
||||
}}
|
||||
>
|
||||
<ChevronLeft size={20} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
on:click={() => {
|
||||
carousel?.scrollTo({ left: scrollX + carousel?.clientWidth * 0.8, behavior: 'smooth' });
|
||||
}}
|
||||
>
|
||||
<ChevronRight size={20} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<Container horizontal>
|
||||
<div
|
||||
class={classNames(
|
||||
'flex overflow-x-scroll items-center overflow-y-visible gap-4 relative scrollbar-hide p-1',
|
||||
scrollClass
|
||||
)}
|
||||
bind:this={carousel}
|
||||
tabindex="-1"
|
||||
on:scroll={() => (scrollX = carousel?.scrollLeft || scrollX)}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</Container>
|
||||
{#if scrollX > 50}
|
||||
<div
|
||||
transition:fade={{ duration: 200 }}
|
||||
class={'absolute inset-y-0 left-0 w-0 sm:w-16 md:w-24 bg-gradient-to-r ' +
|
||||
gradientFromColor}
|
||||
/>
|
||||
{/if}
|
||||
{#if carousel && scrollX < carousel?.scrollWidth - carousel?.clientWidth - 50}
|
||||
<div
|
||||
transition:fade={{ duration: 200 }}
|
||||
class={'absolute inset-y-0 right-0 w-0 sm:w-16 md:w-24 bg-gradient-to-l ' +
|
||||
gradientFromColor}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import CardPlaceholder from '../Card/CardPlaceholder.svelte';
|
||||
import Container from '../../../Container.svelte';
|
||||
export let size: 'dynamic' | 'md' | 'lg' = 'md';
|
||||
export let orientation: 'landscape' | 'portrait' = 'landscape';
|
||||
</script>
|
||||
|
||||
{#each Array(10) as _, i (i)}
|
||||
<Container>
|
||||
<CardPlaceholder {size} index={i} {orientation} />
|
||||
</Container>
|
||||
{/each}
|
||||
19
src/lib/components-new/IconButton.svelte
Normal file
19
src/lib/components-new/IconButton.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import classNames from 'classnames';
|
||||
|
||||
export let disabled = false;
|
||||
</script>
|
||||
|
||||
<button
|
||||
class={classNames(
|
||||
'text-zinc-300 hover:text-zinc-50 p-1 flex items-center justify-center selectable rounded-sm flex-shrink-0',
|
||||
{
|
||||
'opacity-30 cursor-not-allowed pointer-events-none': disabled,
|
||||
'cursor-pointer': !disabled
|
||||
},
|
||||
$$restProps.class
|
||||
)}
|
||||
on:click
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
32
src/lib/components-new/LazyImg.svelte
Normal file
32
src/lib/components-new/LazyImg.svelte
Normal file
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import classNames from 'classnames';
|
||||
|
||||
export let src: string;
|
||||
export let alt: string = '';
|
||||
|
||||
let loaded = false;
|
||||
|
||||
function handleLoad() {
|
||||
loaded = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={classNames(
|
||||
'transition-opacity duration-300',
|
||||
{
|
||||
'opacity-0': !loaded,
|
||||
'opacity-100': loaded
|
||||
},
|
||||
$$restProps.class
|
||||
)}
|
||||
>
|
||||
<img
|
||||
{src}
|
||||
{alt}
|
||||
style="object-fit: cover; width: 100%; height: 100%;"
|
||||
loading="lazy"
|
||||
on:load={handleLoad}
|
||||
/>
|
||||
<slot />
|
||||
</div>
|
||||
16
src/lib/components-new/PlayButton.svelte
Normal file
16
src/lib/components-new/PlayButton.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<script>
|
||||
import { TriangleRight } from 'radix-icons-svelte';
|
||||
import classNames from 'classnames';
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={classNames(
|
||||
$$restProps.class,
|
||||
'backdrop-blur-lg rounded-full bg-[#00000044] text-zinc-300 hover:text-zinc-200 p-2'
|
||||
)}
|
||||
on:click
|
||||
>
|
||||
<TriangleRight size={30} />
|
||||
</div>
|
||||
121
src/lib/components-new/Poster.svelte
Normal file
121
src/lib/components-new/Poster.svelte
Normal file
@@ -0,0 +1,121 @@
|
||||
<script lang="ts">
|
||||
import classNames from 'classnames';
|
||||
import PlayButton from './PlayButton.svelte';
|
||||
import ProgressBar from './ProgressBar.svelte';
|
||||
// import { playerState } from '../VideoPlayer/VideoPlayer';
|
||||
import LazyImg from './LazyImg.svelte';
|
||||
import { Star } from 'radix-icons-svelte';
|
||||
import type { TitleType } from '../types';
|
||||
|
||||
export let tmdbId: number | undefined = undefined;
|
||||
export let tvdbId: number | undefined = undefined;
|
||||
export let openInModal = true;
|
||||
export let jellyfinId: string = '';
|
||||
export let type: TitleType = 'movie';
|
||||
export let backdropUrl: string;
|
||||
|
||||
export let title = '';
|
||||
export let subtitle = '';
|
||||
export let rating: number | undefined = undefined;
|
||||
export let progress = 0;
|
||||
|
||||
export let shadow = false;
|
||||
export let size: 'dynamic' | 'md' | 'lg' | 'sm' = 'md';
|
||||
export let orientation: 'portrait' | 'landscape' = 'landscape';
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click={() => {
|
||||
if (openInModal) {
|
||||
if (tmdbId) {
|
||||
//openTitleModal({ type, id: tmdbId, provider: 'tmdb' });
|
||||
} else if (tvdbId) {
|
||||
//openTitleModal({ type, id: tvdbId, provider: 'tvdb' });
|
||||
}
|
||||
} else {
|
||||
window.location.href = tmdbId || tvdbId ? `/${type}/${tmdbId || tvdbId}` : '#';
|
||||
}
|
||||
}}
|
||||
class={classNames(
|
||||
'relative flex rounded-xl selectable group hover:text-inherit flex-shrink-0 overflow-hidden text-left',
|
||||
{
|
||||
'aspect-video': orientation === 'landscape',
|
||||
'aspect-[2/3]': orientation === 'portrait',
|
||||
'w-32 h-48': size === 'sm' && orientation === 'portrait',
|
||||
'h-32 w-56': size === 'sm' && orientation === 'landscape',
|
||||
'w-44 h-64': size === 'md' && orientation === 'portrait',
|
||||
'h-44 w-80': size === 'md' && orientation === 'landscape',
|
||||
'w-60 h-96': size === 'lg' && orientation === 'portrait',
|
||||
'h-60 w-96': size === 'lg' && orientation === 'landscape',
|
||||
'w-full': size === 'dynamic',
|
||||
'shadow-lg': shadow
|
||||
}
|
||||
)}
|
||||
>
|
||||
<LazyImg src={backdropUrl} class="absolute inset-0 group-hover:scale-105 transition-transform" />
|
||||
<div
|
||||
class="absolute inset-0 opacity-0 group-hover:opacity-30 transition-opacity bg-black"
|
||||
style="filter: blur(50px); transform: scale(3);"
|
||||
>
|
||||
<LazyImg src={backdropUrl} />
|
||||
</div>
|
||||
<!-- <div
|
||||
style={`background-image: url(${backdropUrl}); background-size: cover; background-position: center; filter: blur(50px); transform: scale(3);`}
|
||||
class="absolute inset-0 opacity-0 group-hover:opacity-30 transition-opacity bg-black"
|
||||
/> -->
|
||||
<div
|
||||
class={classNames(
|
||||
'flex-1 flex flex-col justify-between bg-black bg-opacity-40 opacity-0 group-hover:opacity-100 transition-opacity z-[1]',
|
||||
{
|
||||
'py-2 px-3': true
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div class="flex justify-self-start justify-between">
|
||||
<slot name="top-left">
|
||||
<div>
|
||||
<h1 class="text-zinc-100 font-bold line-clamp-2 text-lg">{title}</h1>
|
||||
<h2 class="text-zinc-300 text-sm font-medium line-clamp-2">{subtitle}</h2>
|
||||
</div>
|
||||
</slot>
|
||||
<slot name="top-right">
|
||||
<div />
|
||||
</slot>
|
||||
</div>
|
||||
<div class="flex justify-self-end justify-between">
|
||||
<slot name="bottom-left">
|
||||
<div>
|
||||
{#if rating}
|
||||
<h2 class="flex items-center gap-1.5 text-sm text-zinc-300 font-medium">
|
||||
<Star />{rating.toFixed(1)}
|
||||
</h2>
|
||||
{/if}
|
||||
</div>
|
||||
</slot>
|
||||
<slot name="bottom-right">
|
||||
<div />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div
|
||||
class="absolute inset-0 bg-gradient-to-t from-darken group-hover:opacity-0 transition-opacity z-[1]"
|
||||
/> -->
|
||||
{#if jellyfinId}
|
||||
<div class="absolute inset-0 flex items-center justify-center z-[1]">
|
||||
<PlayButton
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
jellyfinId && true; //playerState.streamJellyfinId(jellyfinId);
|
||||
}}
|
||||
class="sm:opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if progress}
|
||||
<div
|
||||
class="absolute bottom-2 lg:bottom-3 inset-x-2 lg:inset-x-3 bg-gradient-to-t ease-in-out z-[1]"
|
||||
>
|
||||
<ProgressBar {progress} />
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
16
src/lib/components-new/ProgressBar.svelte
Normal file
16
src/lib/components-new/ProgressBar.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let progress = 0;
|
||||
let mounted = false;
|
||||
onMount(() => {
|
||||
mounted = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="h-1 bg-zinc-200 bg-opacity-20 rounded-full overflow-hidden">
|
||||
<div
|
||||
style={'max-width: ' + (mounted ? progress : 0) + '%'}
|
||||
class="h-full bg-zinc-200 bg-opacity-80 transition-[max-width] delay-200 duration-500"
|
||||
/>
|
||||
</div>
|
||||
@@ -3,9 +3,9 @@
|
||||
createJellyfinItemStore,
|
||||
createRadarrMovieStore,
|
||||
createSonarrSeriesStore
|
||||
} from '$lib/stores/data.store';
|
||||
import type { TitleType } from '$lib/types';
|
||||
import { formatMinutesToTime } from '$lib/utils';
|
||||
} from '../../../lib/stores/data.store';
|
||||
import type { TitleType } from '../../../lib/types';
|
||||
import { formatMinutesToTime } from '../../../lib/utils';
|
||||
import classNames from 'classnames';
|
||||
import { Clock, Star } from 'radix-icons-svelte';
|
||||
import { openTitleModal } from '../../stores/modal.store';
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { TmdbMovie2, TmdbSeries2 } from '$lib/apis/tmdb/tmdbApi';
|
||||
import type { TmdbMovie2, TmdbSeries2 } from '../../../lib/apis/tmdb/tmdbApi';
|
||||
import {
|
||||
TMDB_MOVIE_GENRES,
|
||||
TMDB_SERIES_GENRES,
|
||||
getTmdbMovieBackdrop,
|
||||
getTmdbSeriesBackdrop
|
||||
} from '$lib/apis/tmdb/tmdbApi';
|
||||
} from '../../../lib/apis/tmdb/tmdbApi';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import type Card from './Card.svelte';
|
||||
import { TMDB_BACKDROP_SMALL } from '$lib/constants';
|
||||
import { TMDB_BACKDROP_SMALL } from '../../../lib/constants';
|
||||
|
||||
export const fetchCardTmdbMovieProps = async (movie: TmdbMovie2): Promise<ComponentProps<Card>> => {
|
||||
const backdropUri = await getTmdbMovieBackdrop(movie.id || 0);
|
||||
|
||||
@@ -1,30 +1,12 @@
|
||||
<script lang="ts">
|
||||
import CardPlaceholder from '../Card/CardPlaceholder.svelte';
|
||||
import classNames from 'classnames';
|
||||
import Container from '../../../Container.svelte';
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
export let size: 'dynamic' | 'md' | 'lg' = 'md';
|
||||
export let orientation: 'landscape' | 'portrait' = 'landscape';
|
||||
|
||||
export let focusIndex: Readable<number>;
|
||||
let focusWithin: Writable<boolean>;
|
||||
</script>
|
||||
|
||||
<p
|
||||
class={classNames({
|
||||
'bg-blue-500': $focusWithin
|
||||
})}
|
||||
>
|
||||
Index: {$focusIndex}
|
||||
</p>
|
||||
|
||||
{#each Array(10) as _, i (i)}
|
||||
<Container
|
||||
bind:focusWithin
|
||||
class={classNames({
|
||||
'bg-red-500': $focusIndex === i && $focusWithin
|
||||
})}
|
||||
>
|
||||
<Container>
|
||||
<CardPlaceholder {size} index={i} {orientation} />
|
||||
</Container>
|
||||
{/each}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { setJellyfinItemUnwatched, setJellyfinItemWatched } from '$lib/apis/jellyfin/jellyfinApi';
|
||||
import { jellyfinItemsStore } from '$lib/stores/data.store';
|
||||
import {
|
||||
setJellyfinItemUnwatched,
|
||||
setJellyfinItemWatched
|
||||
} from '../../../lib/apis/jellyfin/jellyfinApi';
|
||||
import { jellyfinItemsStore } from '../../../lib/stores/data.store';
|
||||
import classNames from 'classnames';
|
||||
import { Check } from 'radix-icons-svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { TitleType } from '$lib/types';
|
||||
import { TMDB_PROFILE_SMALL } from '$lib/constants';
|
||||
import { openTitleModal } from '$lib/stores/modal.store';
|
||||
import type { TitleType } from '../../../lib/types';
|
||||
import { TMDB_PROFILE_SMALL } from '../../../lib/constants';
|
||||
import { openTitleModal } from '../../../lib/stores/modal.store';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export let tmdbId: number;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { getTmdbPerson } from '$lib/apis/tmdb/tmdbApi';
|
||||
import Carousel from '$lib/components/Carousel/Carousel.svelte';
|
||||
import CarouselPlaceholderItems from '$lib/components/Carousel/CarouselPlaceholderItems.svelte';
|
||||
import Poster from '$lib/components/Poster/Poster.svelte';
|
||||
import TitlePageLayout from '$lib/components/TitlePageLayout/TitlePageLayout.svelte';
|
||||
import FacebookIcon from '$lib/components/svgs/FacebookIcon.svelte';
|
||||
import ImdbIcon from '$lib/components/svgs/ImdbIcon.svelte';
|
||||
import TiktokIcon from '$lib/components/svgs/TiktokIcon.svelte';
|
||||
import TmdbIcon from '$lib/components/svgs/TmdbIcon.svelte';
|
||||
import TwitterIcon from '$lib/components/svgs/TwitterIcon.svelte';
|
||||
import YoutubeIcon from '$lib/components/svgs/YoutubeIcon.svelte';
|
||||
import { TMDB_POSTER_SMALL } from '$lib/constants';
|
||||
import { getTmdbPerson } from '../../lib/apis/tmdb/tmdbApi';
|
||||
import Carousel from '../../lib/components/Carousel/Carousel.svelte';
|
||||
import CarouselPlaceholderItems from '../../lib/components/Carousel/CarouselPlaceholderItems.svelte';
|
||||
import Poster from '../../lib/components/Poster/Poster.svelte';
|
||||
import TitlePageLayout from '../../lib/components/TitlePageLayout/TitlePageLayout.svelte';
|
||||
import FacebookIcon from '../../lib/components/svgs/FacebookIcon.svelte';
|
||||
import ImdbIcon from '../../lib/components/svgs/ImdbIcon.svelte';
|
||||
import TiktokIcon from '../../lib/components/svgs/TiktokIcon.svelte';
|
||||
import TmdbIcon from '../../lib/components/svgs/TmdbIcon.svelte';
|
||||
import TwitterIcon from '../../lib/components/svgs/TwitterIcon.svelte';
|
||||
import YoutubeIcon from '../../lib/components/svgs/YoutubeIcon.svelte';
|
||||
import { DotFilled, InstagramLogo } from 'radix-icons-svelte';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import { TMDB_POSTER_SMALL } from '../constants';
|
||||
|
||||
const GENDER_OPTIONS = ['Not set', 'Female', 'Male', 'Non-binary'] as const;
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<script lang="ts">
|
||||
import type { TitleType } from '$lib/types';
|
||||
import classNames from 'classnames';
|
||||
import PlayButton from '../PlayButton.svelte';
|
||||
import ProgressBar from '../ProgressBar.svelte';
|
||||
import { playerState } from '../VideoPlayer/VideoPlayer';
|
||||
import LazyImg from '../LazyImg.svelte';
|
||||
import { Star } from 'radix-icons-svelte';
|
||||
import { openTitleModal } from '$lib/stores/modal.store';
|
||||
import type { TitleType } from '../../types';
|
||||
|
||||
export let tmdbId: number | undefined = undefined;
|
||||
export let tvdbId: number | undefined = undefined;
|
||||
@@ -29,9 +28,9 @@
|
||||
on:click={() => {
|
||||
if (openInModal) {
|
||||
if (tmdbId) {
|
||||
openTitleModal({ type, id: tmdbId, provider: 'tmdb' });
|
||||
//openTitleModal({ type, id: tmdbId, provider: 'tmdb' });
|
||||
} else if (tvdbId) {
|
||||
openTitleModal({ type, id: tvdbId, provider: 'tvdb' });
|
||||
//openTitleModal({ type, id: tvdbId, provider: 'tvdb' });
|
||||
}
|
||||
} else {
|
||||
window.location.href = tmdbId || tvdbId ? `/${type}/${tmdbId || tvdbId}` : '#';
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { getJellyfinEpisodes, type JellyfinItem } from '$lib/apis/jellyfin/jellyfinApi';
|
||||
import { addSeriesToSonarr, type SonarrSeries } from '$lib/apis/sonarr/sonarrApi';
|
||||
import { getJellyfinEpisodes, type JellyfinItem } from '../../lib/apis/jellyfin/jellyfinApi';
|
||||
import { addSeriesToSonarr, type SonarrSeries } from '../../lib/apis/sonarr/sonarrApi';
|
||||
import {
|
||||
getTmdbIdFromTvdbId,
|
||||
getTmdbSeries,
|
||||
getTmdbSeriesRecommendations,
|
||||
getTmdbSeriesSeasons,
|
||||
getTmdbSeriesSimilar
|
||||
} from '$lib/apis/tmdb/tmdbApi';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import Card from '$lib/components/Card/Card.svelte';
|
||||
import { fetchCardTmdbProps } from '$lib/components/Card/card';
|
||||
import Carousel from '$lib/components/Carousel/Carousel.svelte';
|
||||
import CarouselPlaceholderItems from '$lib/components/Carousel/CarouselPlaceholderItems.svelte';
|
||||
import UiCarousel from '$lib/components/Carousel/UICarousel.svelte';
|
||||
import EpisodeCard from '$lib/components/EpisodeCard/EpisodeCard.svelte';
|
||||
import PersonCard from '$lib/components/PersonCard/PersonCard.svelte';
|
||||
import SeriesRequestModal from '$lib/components/RequestModal/SeriesRequestModal.svelte';
|
||||
import OpenInButton from '$lib/components/TitlePageLayout/OpenInButton.svelte';
|
||||
import TitlePageLayout from '$lib/components/TitlePageLayout/TitlePageLayout.svelte';
|
||||
import { playerState } from '$lib/components/VideoPlayer/VideoPlayer';
|
||||
import { TMDB_BACKDROP_SMALL } from '$lib/constants';
|
||||
} from '../../lib/apis/tmdb/tmdbApi';
|
||||
import Button from '../../lib/components/Button.svelte';
|
||||
import Card from '../../lib/components/Card/Card.svelte';
|
||||
import { fetchCardTmdbProps } from '../../lib/components/Card/card';
|
||||
import Carousel from '../../lib/components/Carousel/Carousel.svelte';
|
||||
import CarouselPlaceholderItems from '../../lib/components/Carousel/CarouselPlaceholderItems.svelte';
|
||||
import UiCarousel from '../../lib/components/Carousel/UICarousel.svelte';
|
||||
import EpisodeCard from '../../lib/components/EpisodeCard/EpisodeCard.svelte';
|
||||
import PersonCard from '../../lib/components/PersonCard/PersonCard.svelte';
|
||||
import SeriesRequestModal from '../../lib/components/RequestModal/SeriesRequestModal.svelte';
|
||||
import OpenInButton from '../../lib/components/TitlePageLayout/OpenInButton.svelte';
|
||||
import TitlePageLayout from '../../lib/components/TitlePageLayout/TitlePageLayout.svelte';
|
||||
import { playerState } from '../../lib/components/VideoPlayer/VideoPlayer';
|
||||
import { TMDB_BACKDROP_SMALL } from '../../lib/constants';
|
||||
import {
|
||||
createJellyfinItemStore,
|
||||
createSonarrDownloadStore,
|
||||
createSonarrSeriesStore
|
||||
} from '$lib/stores/data.store';
|
||||
import { modalStack } from '$lib/stores/modal.store';
|
||||
import { settings } from '$lib/stores/settings.store';
|
||||
import type { TitleId } from '$lib/types';
|
||||
import { capitalize, formatMinutesToTime, formatSize } from '$lib/utils';
|
||||
} from '../../lib/stores/data.store';
|
||||
import { modalStack } from '../../lib/stores/modal.store';
|
||||
import { settings } from '../../lib/stores/settings.store';
|
||||
import type { TitleId } from '../../lib/types';
|
||||
import { capitalize, formatMinutesToTime, formatSize } from '../../lib/utils';
|
||||
import classNames from 'classnames';
|
||||
import { Archive, ChevronLeft, ChevronRight, DotFilled, Plus } from 'radix-icons-svelte';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { TMDB_IMAGES_ORIGINAL, TMDB_POSTER_SMALL } from '$lib/constants';
|
||||
import type { TitleId, TitleType } from '$lib/types';
|
||||
import { TMDB_IMAGES_ORIGINAL, TMDB_POSTER_SMALL } from '../../../lib/constants';
|
||||
import type { TitleId, TitleType } from '../../../lib/types';
|
||||
import classNames from 'classnames';
|
||||
import { ChevronLeft, Cross2, DotFilled, ExternalLink } from 'radix-icons-svelte';
|
||||
import Carousel from '../Carousel/Carousel.svelte';
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
reportJellyfinPlaybackProgress,
|
||||
reportJellyfinPlaybackStarted,
|
||||
reportJellyfinPlaybackStopped
|
||||
} from '$lib/apis/jellyfin/jellyfinApi';
|
||||
import getDeviceProfile from '$lib/apis/jellyfin/playback-profiles';
|
||||
import { getQualities } from '$lib/apis/jellyfin/qualities';
|
||||
import { settings } from '$lib/stores/settings.store';
|
||||
} from '../../apis/jellyfin/jellyfinApi';
|
||||
import getDeviceProfile from '../../apis/jellyfin/playback-profiles';
|
||||
import { getQualities } from '../../apis/jellyfin/qualities';
|
||||
import { settings } from '../../stores/settings.store';
|
||||
import classNames from 'classnames';
|
||||
import Hls from 'hls.js';
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { jellyfinItemsStore } from '$lib/stores/data.store';
|
||||
import { writable } from 'svelte/store';
|
||||
import { modalStack } from '../../stores/modal.store';
|
||||
import VideoPlayer from './VideoPlayer.svelte';
|
||||
import { jellyfinItemsStore } from '../../stores/data.store';
|
||||
|
||||
const initialValue = { visible: false, jellyfinId: '' };
|
||||
export type PlayerStateValue = typeof initialValue;
|
||||
|
||||
@@ -1,5 +1,81 @@
|
||||
<script lang="ts">
|
||||
import Container from '../../Container.svelte';
|
||||
</script>
|
||||
|
||||
<Container focusOnMount>SeriesPage</Container>
|
||||
<script lang="ts">
|
||||
import Container from '../../Container.svelte';
|
||||
import { getPosterProps, TmdbApiOpen } from '../apis/tmdb/tmdbApi';
|
||||
import { formatDateToYearMonthDay } from '../utils';
|
||||
import { settings } from '../stores/settings.store';
|
||||
import type { TitleType } from '../types';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import Poster from '../components-new/Poster.svelte';
|
||||
import type { JellyfinItem } from '../apis/jellyfin/jellyfinApi';
|
||||
import { jellyfinItemsStore } from '../stores/data.store';
|
||||
import Carousel from '../components-new/Carousel/Carousel.svelte';
|
||||
import { _ } from 'svelte-i18n';
|
||||
import CarouselPlaceholderItems from '../components-new/Carousel/CarouselPlaceholderItems.svelte';
|
||||
|
||||
const jellyfinItemsPromise = new Promise<JellyfinItem[]>((resolve) => {
|
||||
jellyfinItemsStore.subscribe((data) => {
|
||||
if (data.loading) return;
|
||||
resolve(data.data || []);
|
||||
});
|
||||
});
|
||||
|
||||
const fetchCardProps = async (
|
||||
items: {
|
||||
name?: string;
|
||||
title?: string;
|
||||
id?: number;
|
||||
vote_average?: number;
|
||||
number_of_seasons?: number;
|
||||
first_air_date?: string;
|
||||
poster_path?: string;
|
||||
}[],
|
||||
type: TitleType | undefined = undefined
|
||||
): Promise<ComponentProps<Poster>[]> => {
|
||||
const filtered = $settings.discover.excludeLibraryItems
|
||||
? items.filter(
|
||||
async (item) =>
|
||||
!(await jellyfinItemsPromise).find((i) => i.ProviderIds?.Tmdb === String(item.id))
|
||||
)
|
||||
: items;
|
||||
|
||||
return Promise.all(filtered.map(async (item) => getPosterProps(item, type))).then((props) =>
|
||||
props.filter((p) => p.backdropUrl)
|
||||
);
|
||||
};
|
||||
|
||||
const fetchNowStreaming = () =>
|
||||
TmdbApiOpen.GET('/3/discover/tv', {
|
||||
params: {
|
||||
query: {
|
||||
'air_date.gte': formatDateToYearMonthDay(new Date()),
|
||||
'first_air_date.lte': formatDateToYearMonthDay(new Date()),
|
||||
sort_by: 'popularity.desc',
|
||||
language: $settings.language,
|
||||
with_original_language: parseIncludedLanguages($settings.discover.includedLanguages)
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((res) => res.data?.results || [])
|
||||
.then((i) => fetchCardProps(i, 'series'));
|
||||
|
||||
function parseIncludedLanguages(includedLanguages: string) {
|
||||
return includedLanguages.replace(' ', '').split(',').join('|');
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container focusOnMount>
|
||||
<Carousel scrollClass="px-2 sm:px-8 2xl:px-16">
|
||||
<div slot="title" class="text-lg font-semibold text-zinc-300">
|
||||
{$_('discover.streamingNow')}
|
||||
</div>
|
||||
{#await fetchNowStreaming()}
|
||||
<CarouselPlaceholderItems />
|
||||
{:then props}
|
||||
{#each props as prop (prop.tmdbId)}
|
||||
<Container>
|
||||
<Poster {...prop} />
|
||||
</Container>
|
||||
{/each}
|
||||
{/await}
|
||||
</Carousel>
|
||||
</Container>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<widget xmlns:tizen="http://tizen.org/ns/widgets" xmlns="http://www.w3.org/ns/widgets" id="http://yourdomain/SvelteTizen" version="1.0.0" viewmodes="maximized">
|
||||
<tizen:application id="JZUbM5WimZ.SvelteTizen" package="JZUbM5WimZ" required_version="2.3"/>
|
||||
<content src="index.html"/>
|
||||
<tizen:content-security-policy>default-src 'self'</tizen:content-security-policy>
|
||||
<feature name="http://tizen.org/feature/screen.size.normal.1080.1920"/>
|
||||
<icon src="icon.png"/>
|
||||
<tizen:metadata key="http://tizen.org/metadata/app_ui_type/base_screen_resolution" value="extensive"/>
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"isolatedModules": true
|
||||
"isolatedModules": true,
|
||||
"paths": {
|
||||
"$lib/*": ["src/lib/*"],
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
|
||||
Reference in New Issue
Block a user