mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-23 09:15:11 +02:00
Initial work on library page
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import '../app.css';
|
||||
import { setClient } from 'svelte-apollo';
|
||||
import Navbar from './Navbar.svelte';
|
||||
import Navbar from './components/Navbar.svelte';
|
||||
</script>
|
||||
|
||||
<div class="app">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import SmallPoster from './SmallPoster.svelte';
|
||||
import SmallPoster from './components/SmallPoster/SmallPoster.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import ResourceDetails from './ResourceDetails/ResourceDetails.svelte';
|
||||
import ResourceDetails from './components/ResourceDetails/ResourceDetails.svelte';
|
||||
import ResourceDetailsControls from './ResourceDetailsControls.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -18,6 +18,6 @@
|
||||
<ChevronRight size="24" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute inset-x-0 bottom-6 flex justify-center mx-auto opacity-50">
|
||||
<ChevronDown size="20" />
|
||||
</div>
|
||||
<!--<div class="absolute inset-x-0 bottom-6 flex justify-center mx-auto opacity-50">-->
|
||||
<!-- <ChevronDown size="20" />-->
|
||||
<!--</div>-->
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
remoteResource.backdrop_path +
|
||||
"')"}
|
||||
>
|
||||
<div class="youtube-container absolute h-full scale-[150%]">
|
||||
<div class="youtube-container absolute h-full scale-[150%] hidden sm:block">
|
||||
{#if video.key}
|
||||
<iframe
|
||||
class={classNames('transition-opacity', {
|
||||
@@ -86,7 +86,7 @@
|
||||
</div>
|
||||
<div
|
||||
class={classNames(
|
||||
'bg-gradient-to-b from-[#070501bf] via-20% via-transparent transition-opacity absolute inset-0 z-[1]',
|
||||
'bg-gradient-to-b from-darken via-20% via-transparent transition-opacity absolute inset-0 z-[1]',
|
||||
{
|
||||
'opacity-100': focusTrailer,
|
||||
'opacity-0': !focusTrailer
|
||||
@@ -95,12 +95,12 @@
|
||||
/>
|
||||
<div
|
||||
class={classNames(
|
||||
'h-full w-full px-16 pb-12 pt-32',
|
||||
'h-full w-full px-16 pb-8 pt-32',
|
||||
'grid grid-cols-[1fr_max-content] grid-rows-[1fr_min-content] gap-x-16 gap-y-8 relative z-[2]',
|
||||
'transition-colors',
|
||||
{
|
||||
'bg-[#070501bf]': !focusTrailer,
|
||||
'bg-[#00000000]': focusTrailer
|
||||
'bg-darken': !focusTrailer,
|
||||
'bg-transparent': focusTrailer
|
||||
}
|
||||
)}
|
||||
>
|
||||
15
src/routes/components/SmallHorizontalPoster/PosterTag.svelte
Normal file
15
src/routes/components/SmallHorizontalPoster/PosterTag.svelte
Normal file
@@ -0,0 +1,15 @@
|
||||
<script>
|
||||
import classNames from 'classnames';
|
||||
|
||||
export let value = '';
|
||||
export let filled = false;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={classNames('border rounded p-[0px] px-1 text-[10px] font-medium', {
|
||||
'text-zinc-200 border-zinc-500': !filled,
|
||||
'bg-zinc-200 border-zinc-200 text-zinc-900': filled
|
||||
})}
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import type { TmdbMovieFull } from '$lib/tmdb-api';
|
||||
import { formatGenres, getRuntime } from '$lib/utils';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export let tmdbMovie: TmdbMovieFull;
|
||||
|
||||
export let available = true;
|
||||
export let progress = 0;
|
||||
export let progressType: 'watched' | 'downloading' = 'watched';
|
||||
export let randomProgress = false;
|
||||
if (randomProgress) {
|
||||
progress = Math.random() > 0.3 ? Math.random() * 100 : 0;
|
||||
}
|
||||
|
||||
const backdropUrl =
|
||||
'https://www.themoviedb.org/t/p/original' +
|
||||
tmdbMovie.images.backdrops.filter((b) => b.iso_639_1 === 'en')[0].file_path;
|
||||
</script>
|
||||
|
||||
<div
|
||||
style={"background-image: url('" + backdropUrl + "')"}
|
||||
class="bg-center bg-cover h-40 w-72 rounded overflow-hidden relative drop-shadow-2xl"
|
||||
>
|
||||
<div style={'width: ' + progress + '%'} class="h-[2px] bg-zinc-200 bottom-0 absolute z-[1]" />
|
||||
<div
|
||||
on:click={() => window.open('/movie/' + tmdbMovie.id, '_self')}
|
||||
class="h-full w-full opacity-0 hover:opacity-100 transition-opacity flex flex-col justify-between cursor-pointer p-2 px-3 relative z-[1] peer"
|
||||
style={progress > 0 ? 'padding-bottom: 0.6rem;' : ''}
|
||||
>
|
||||
<div>
|
||||
<h1 class="font-bold tracking-wider">{tmdbMovie.original_title}</h1>
|
||||
<div class="text-xs text-zinc-300 tracking-wider font-medium">
|
||||
{formatGenres(tmdbMovie.genres)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-end">
|
||||
{#if progressType === 'watched'}
|
||||
<div class="text-xs font-medium text-zinc-200">
|
||||
{progress
|
||||
? getRuntime(tmdbMovie.runtime - tmdbMovie.runtime * (progress / 100)) + ' left'
|
||||
: getRuntime(tmdbMovie.runtime)}
|
||||
</div>
|
||||
{:else if progressType === 'downloading'}
|
||||
<div class="text-xs font-medium text-zinc-200">
|
||||
{Math.floor(progress) + '% Downloaded'}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class={classNames('absolute inset-0', {
|
||||
'bg-darken opacity-0 peer-hover:opacity-100': available,
|
||||
'bg-[#00000055] peer-hover:bg-darken': !available
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
@@ -3,14 +3,15 @@
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let tmdbId;
|
||||
export let progress = 0;
|
||||
export let randomProgress = false;
|
||||
if (randomProgress) progress = Math.random() > 0.5 ? Math.round(Math.random() * 100) : 100;
|
||||
|
||||
export let type: 'movie' | 'tv' = 'movie';
|
||||
|
||||
let bg = '';
|
||||
let title = 'Loading...';
|
||||
|
||||
const progress = Math.random() > 0.5 ? Math.round(Math.random() * 100) : 100;
|
||||
|
||||
onMount(() => {
|
||||
TmdbApi.get('/' + type + '/' + tmdbId)
|
||||
.then((res) => res.data)
|
||||
@@ -35,9 +36,7 @@
|
||||
class="bg-center bg-cover aspect-[2/3] h-72 shadow-2xl m-1.5"
|
||||
style={"background-image: url('" + bg + "')"}
|
||||
>
|
||||
<div
|
||||
class="w-full h-full bg-gradient-to-b from-[#00000099] via-20% via-transparent hover:bg-[#00000099] transition-all flex"
|
||||
>
|
||||
<div class="w-full h-full hover:bg-darken transition-all flex">
|
||||
<div
|
||||
class="opacity-0 group-hover:opacity-100 transition-opacity p-2 flex flex-col justify-between flex-1 cursor-pointer"
|
||||
>
|
||||
@@ -1,6 +1,75 @@
|
||||
<div class="pt-24">
|
||||
Contains all the titles available locally, the ones already watched previously (greyed out at the
|
||||
bottom), and the ones that are in some sort of watchlist and available via any source.
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import SmallHorizontalPoster from '../components/SmallHorizontalPoster/SmallHorizontalPoster.svelte';
|
||||
import type { TmdbMovieFull } from '$lib/tmdb-api';
|
||||
export let data: PageData;
|
||||
console.log(data);
|
||||
|
||||
<div>Library</div>
|
||||
const allMovies: Record<string, TmdbMovieFull> = {};
|
||||
data.tmdbMovies.forEach((m) => (allMovies[m.id] = m));
|
||||
|
||||
const tmdbIdToDownloading = {};
|
||||
(data.downloading as any).forEach((d) => (tmdbIdToDownloading[d.movie.tmdbId] = d));
|
||||
|
||||
const tmdbIdToRadarrMovie = {};
|
||||
(data.radarrMovies as any).forEach((r) => (tmdbIdToRadarrMovie[r.tmdbId] = r));
|
||||
|
||||
const downloading = data.tmdbMovies.filter((m) => tmdbIdToDownloading[m.id] !== undefined);
|
||||
const available = data.tmdbMovies.filter((m) => tmdbIdToDownloading[m.id] === undefined);
|
||||
const unavailable = data.tmdbMovies.filter(
|
||||
(m) => !tmdbIdToRadarrMovie[m.id]?.hasFile && !tmdbIdToDownloading[m.id]
|
||||
);
|
||||
const watched = [];
|
||||
|
||||
const posterGridStyle = 'flex flex-wrap justify-center gap-x-4 gap-y-8';
|
||||
const headerStyle = 'uppercase tracking-widest font-bold text-center mt-2';
|
||||
</script>
|
||||
|
||||
<div
|
||||
style="background-image: url('https://www.themoviedb.org/t/p/original/vvjYv7bSWerbsi0LsMjLnTVOX7c.jpg')"
|
||||
>
|
||||
<div class="py-24 backdrop-blur-2xl bg-darken px-8 flex flex-col gap-4">
|
||||
<!-- Contains all the titles available locally, the ones already watched previously (greyed out at the-->
|
||||
<!-- bottom), and the ones that are in some sort of watchlist and not available via any source.-->
|
||||
|
||||
<!-- <div>Library</div>-->
|
||||
|
||||
{#if downloading.length > 0}
|
||||
<h1 class={headerStyle}>Downloading</h1>
|
||||
<div class={posterGridStyle}>
|
||||
{#each downloading as movie (movie.id)}
|
||||
<SmallHorizontalPoster
|
||||
progress={(tmdbIdToDownloading[movie.id].sizeleft /
|
||||
tmdbIdToDownloading[movie.id].size) *
|
||||
100}
|
||||
progressType="downloading"
|
||||
available={false}
|
||||
tmdbMovie={movie}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if available.length > 0}
|
||||
<h1 class={headerStyle}>Available</h1>
|
||||
<div class={posterGridStyle}>
|
||||
{#each available as movie (movie.id)}
|
||||
<SmallHorizontalPoster randomProgress={true} tmdbMovie={movie} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if unavailable.length > 0}
|
||||
<h1 class={headerStyle}>Unavailable</h1>
|
||||
<div class={posterGridStyle}>
|
||||
{#each unavailable as movie (movie.id)}
|
||||
<SmallHorizontalPoster available={false} tmdbMovie={movie} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if watched.length > 0}
|
||||
<h1 class={headerStyle}>Watched</h1>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
34
src/routes/library/+page.ts
Normal file
34
src/routes/library/+page.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { PageLoad } from './$types';
|
||||
import { radarrApi } from '$lib/servarr-api';
|
||||
import { fetchMovieDetails } from '$lib/tmdb-api';
|
||||
|
||||
export const load = (async () => {
|
||||
const radarrMovies = await radarrApi
|
||||
.get('/api/v3/movie', {
|
||||
params: {}
|
||||
})
|
||||
.then((r) => r.data);
|
||||
|
||||
let tmdbMovies;
|
||||
if (radarrMovies) {
|
||||
tmdbMovies = await Promise.all(
|
||||
radarrMovies.filter((m) => m.tmdbId).map((m) => fetchMovieDetails(m.tmdbId as any))
|
||||
);
|
||||
}
|
||||
|
||||
console.log('radarrMovies', radarrMovies);
|
||||
|
||||
return {
|
||||
radarrMovies,
|
||||
tmdbMovies,
|
||||
downloading: await radarrApi
|
||||
.get('/api/v3/queue', {
|
||||
params: {
|
||||
query: {
|
||||
includeMovie: true
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((r) => r.data?.records)
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import ResourceDetails from '../../ResourceDetails/ResourceDetails.svelte';
|
||||
import ResourceDetails from '../../components/ResourceDetails/ResourceDetails.svelte';
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<ResourceDetails trailer={false} resource={data.movie} remoteResource={data.remoteMovie} />
|
||||
<ResourceDetails resource={data.movie} remoteResource={data.remoteMovie} />
|
||||
|
||||
Reference in New Issue
Block a user