mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-27 11:05:13 +02:00
Project refactoring
This commit is contained in:
@@ -1,129 +0,0 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
createJellyfinItemStore,
|
||||
createRadarrMovieStore,
|
||||
createSonarrSeriesStore
|
||||
} 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';
|
||||
import ContextMenu from '../ContextMenu/ContextMenu.svelte';
|
||||
import LibraryItemContextItems from '../ContextMenu/LibraryItemContextItems.svelte';
|
||||
import ProgressBar from '../ProgressBar.svelte';
|
||||
|
||||
export let tmdbId: number;
|
||||
export let type: TitleType = 'movie';
|
||||
export let title: string;
|
||||
export let genres: string[] = [];
|
||||
export let runtimeMinutes = 0;
|
||||
export let seasons = 0;
|
||||
export let completionTime = '';
|
||||
export let backdropUrl: string;
|
||||
export let rating: number;
|
||||
|
||||
export let available = true;
|
||||
export let progress = 0;
|
||||
export let size: 'dynamic' | 'md' | 'lg' = 'md';
|
||||
export let openInModal = true;
|
||||
|
||||
let jellyfinItemStore = createJellyfinItemStore(tmdbId);
|
||||
let radarrMovieStore = createRadarrMovieStore(tmdbId);
|
||||
let sonarrSeriesStore = createSonarrSeriesStore(title);
|
||||
</script>
|
||||
|
||||
<ContextMenu heading={title}>
|
||||
<svelte:fragment slot="menu">
|
||||
<LibraryItemContextItems
|
||||
jellyfinItem={$jellyfinItemStore.item}
|
||||
radarrMovie={$radarrMovieStore.item}
|
||||
sonarrSeries={$sonarrSeriesStore.item}
|
||||
{type}
|
||||
{tmdbId}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<button
|
||||
class={classNames(
|
||||
'rounded overflow-hidden relative shadow-lg shrink-0 aspect-video selectable hover:text-inherit flex flex-col justify-between group placeholder-image',
|
||||
'p-2 px-3 gap-2',
|
||||
{
|
||||
'h-40': size === 'md',
|
||||
'h-60': size === 'lg',
|
||||
'w-full': size === 'dynamic'
|
||||
}
|
||||
)}
|
||||
on:click={() => {
|
||||
if (openInModal) {
|
||||
openTitleModal({ type, id: tmdbId, provider: 'tmdb' });
|
||||
} else {
|
||||
window.location.href = `/${type}/${tmdbId}`;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={"background-image: url('" + backdropUrl + "')"}
|
||||
class="absolute inset-0 bg-center bg-cover group-hover:scale-105 group-focus-visible:scale-105 transition-transform"
|
||||
/>
|
||||
<div
|
||||
class={classNames(
|
||||
'absolute inset-0 transition-opacity bg-darken sm:bg-opacity-100 bg-opacity-50',
|
||||
{
|
||||
'opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100': available
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
class="flex flex-col justify-between flex-1 transition-opacity cursor-pointer relative opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100"
|
||||
>
|
||||
<div class="text-left">
|
||||
<h1 class="font-bold tracking-wider text-lg">{title}</h1>
|
||||
<div class="text-xs text-zinc-300 tracking-wider font-medium">
|
||||
{genres.map((genre) => genre.charAt(0).toUpperCase() + genre.slice(1)).join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-end">
|
||||
{#if completionTime}
|
||||
<div class="text-sm font-medium text-zinc-200 tracking-wide">
|
||||
Downloaded in <b
|
||||
>{formatMinutesToTime(
|
||||
(new Date(completionTime).getTime() - Date.now()) / 1000 / 60
|
||||
)}</b
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
{#if runtimeMinutes}
|
||||
<div class="flex gap-1.5 items-center">
|
||||
<Clock />
|
||||
<div class="text-sm text-zinc-200">
|
||||
{progress
|
||||
? formatMinutesToTime(runtimeMinutes - runtimeMinutes * (progress / 100)) +
|
||||
' left'
|
||||
: formatMinutesToTime(runtimeMinutes)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if seasons}
|
||||
<div class="text-sm text-zinc-200">
|
||||
{seasons} Season{seasons > 1 ? 's' : ''}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if rating}
|
||||
<div class="flex gap-1.5 items-center">
|
||||
<Star />
|
||||
<div class="text-sm text-zinc-200">
|
||||
{rating.toFixed(1)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if progress}
|
||||
<div class="relative">
|
||||
<ProgressBar {progress} />
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
</ContextMenu>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div class="grid gap-x-4 gap-y-8 grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
||||
<slot />
|
||||
</div>
|
||||
@@ -1,23 +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"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<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>
|
||||
@@ -1,62 +0,0 @@
|
||||
import type { TmdbMovie2, TmdbSeries2 } from '../../../lib/apis/tmdb/tmdbApi';
|
||||
import {
|
||||
TMDB_MOVIE_GENRES,
|
||||
TMDB_SERIES_GENRES,
|
||||
getTmdbMovieBackdrop,
|
||||
getTmdbSeriesBackdrop
|
||||
} from '../../../lib/apis/tmdb/tmdbApi';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import type Card from './Card.svelte';
|
||||
import { TMDB_BACKDROP_SMALL } from '../../../lib/constants';
|
||||
|
||||
export const fetchCardTmdbMovieProps = async (movie: TmdbMovie2): Promise<ComponentProps<Card>> => {
|
||||
const backdropUri = await getTmdbMovieBackdrop(movie.id || 0);
|
||||
|
||||
const movieAny = movie as any;
|
||||
const genres =
|
||||
movie.genres?.map((g) => g.name || '') ||
|
||||
movieAny?.genre_ids?.map(
|
||||
(id: number) => TMDB_MOVIE_GENRES.find((g) => g.id === id)?.name || ''
|
||||
) ||
|
||||
[];
|
||||
|
||||
return {
|
||||
tmdbId: movie.id || 0,
|
||||
title: movie.title || '',
|
||||
genres,
|
||||
runtimeMinutes: movie.runtime,
|
||||
backdropUrl: backdropUri ? TMDB_BACKDROP_SMALL + backdropUri : '',
|
||||
rating: movie.vote_average || 0
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchCardTmdbSeriesProps = async (
|
||||
series: TmdbSeries2
|
||||
): Promise<ComponentProps<Card>> => {
|
||||
const backdropUri = await getTmdbSeriesBackdrop(series.id || 0);
|
||||
|
||||
const seriesAny = series as any;
|
||||
const genres =
|
||||
series.genres?.map((g) => g.name || '') ||
|
||||
seriesAny?.genre_ids?.map(
|
||||
(id: number) => TMDB_SERIES_GENRES.find((g) => g.id === id)?.name || ''
|
||||
) ||
|
||||
[];
|
||||
|
||||
return {
|
||||
tmdbId: series.id || 0,
|
||||
title: series.name || '',
|
||||
genres,
|
||||
runtimeMinutes: series.episode_run_time?.[0],
|
||||
backdropUrl: backdropUri ? TMDB_BACKDROP_SMALL + backdropUri : '',
|
||||
rating: series.vote_average || 0,
|
||||
type: 'series'
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchCardTmdbProps = async (
|
||||
item: TmdbSeries2 | TmdbMovie2
|
||||
): Promise<ComponentProps<Card>> => {
|
||||
if ('name' in item) return fetchCardTmdbSeriesProps(item);
|
||||
return fetchCardTmdbMovieProps(item);
|
||||
};
|
||||
Reference in New Issue
Block a user