Project refactoring

This commit is contained in:
Aleksi Lassila
2024-03-02 00:52:01 +02:00
parent 1c1fbbf043
commit 27b9fc57b3
94 changed files with 128 additions and 102 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"
/>

View File

@@ -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>

View File

@@ -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);
};