mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-22 00:35:12 +02:00
Added title modals
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
import ContextMenuItem from '../ContextMenu/ContextMenuItem.svelte';
|
||||
import { setJellyfinItemUnwatched, setJellyfinItemWatched } from '$lib/apis/jellyfin/jellyfinApi';
|
||||
import { library } from '$lib/stores/library.store';
|
||||
import { titlePageModal } from '../TitlePageLayout/TitlePageModal';
|
||||
|
||||
export let tmdbId: number;
|
||||
export let jellyfinId: string | undefined = undefined;
|
||||
@@ -48,7 +49,7 @@
|
||||
Mark as unwatched
|
||||
</ContextMenuItem>
|
||||
</svelte:fragment>
|
||||
<a
|
||||
<button
|
||||
class={classNames(
|
||||
'rounded overflow-hidden relative shadow-lg shrink-0 aspect-video selectable block hover:text-inherit',
|
||||
{
|
||||
@@ -57,7 +58,7 @@
|
||||
'w-full': size === 'dynamic'
|
||||
}
|
||||
)}
|
||||
href={`/${type}/${tmdbId}`}
|
||||
on:click={() => titlePageModal.set(tmdbId, type)}
|
||||
>
|
||||
<div
|
||||
style={'width: ' + (progress ? Math.max(progress, 2) : progress) + '%'}
|
||||
@@ -67,7 +68,7 @@
|
||||
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>
|
||||
<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(', ')}
|
||||
@@ -119,5 +120,5 @@
|
||||
'bg-[#00000055] peer-hover:bg-darken': !available
|
||||
})}
|
||||
/>
|
||||
</a>
|
||||
</button>
|
||||
</ContextMenu>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<button
|
||||
class={classNames(
|
||||
'text-zinc-300 hover:text-zinc-50 p-1 flex items-center justify-center selectable rounded-sm',
|
||||
'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
|
||||
|
||||
@@ -6,6 +6,8 @@ function createModalStack() {
|
||||
top: undefined
|
||||
});
|
||||
|
||||
store.subscribe(console.log);
|
||||
|
||||
return {
|
||||
...store,
|
||||
push: (symbol: Symbol) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
import { fly } from 'svelte/transition';
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { SonarrEpisode } from '$lib/apis/sonarr/sonarrApi';
|
||||
import { ChevronRight } from 'radix-icons-svelte';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import Button from '../Button.svelte';
|
||||
import { createModalProps, type ModalProps } from '../Modal/Modal';
|
||||
import Modal from '../Modal/Modal.svelte';
|
||||
import ModalContainer from '../Modal/ModalContainer.svelte';
|
||||
import ModalContent from '../Modal/ModalContent.svelte';
|
||||
import ModalHeader from '../Modal/ModalHeader.svelte';
|
||||
import RoundedButton from '../RoundedButton.svelte';
|
||||
import EpisodeSelectModal from './EpisodeSelectModal.svelte';
|
||||
import RequestModal from './RequestModal.svelte';
|
||||
import type { SonarrEpisode } from '$lib/apis/sonarr/sonarrApi';
|
||||
import Button from '../Button.svelte';
|
||||
import { ChevronRight } from 'radix-icons-svelte';
|
||||
|
||||
export let modalProps: ModalProps;
|
||||
export let sonarrId: number;
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { TMDB_IMAGES_ORIGINAL, TMDB_POSTER_SMALL } from '$lib/constants';
|
||||
import { DotFilled } from 'radix-icons-svelte';
|
||||
import classNames from 'classnames';
|
||||
import { ChevronLeft, DotFilled, ExternalLink } from 'radix-icons-svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import Carousel from './Carousel/Carousel.svelte';
|
||||
import CarouselPlaceholderItems from './Carousel/CarouselPlaceholderItems.svelte';
|
||||
import Carousel from '../Carousel/Carousel.svelte';
|
||||
import CarouselPlaceholderItems from '../Carousel/CarouselPlaceholderItems.svelte';
|
||||
import IconButton from '../IconButton.svelte';
|
||||
|
||||
export let isModal = false;
|
||||
export let handleCloseModal: () => void = () => {};
|
||||
|
||||
export let tmdbId: number;
|
||||
export let type: 'movie' | 'series';
|
||||
|
||||
export let backdropUriCandidates: string[];
|
||||
export let posterPath: string;
|
||||
@@ -12,10 +20,11 @@
|
||||
export let tagline: string;
|
||||
export let overview: string;
|
||||
|
||||
let topHeight: number;
|
||||
let bottomHeight: number;
|
||||
let windowHeight: number;
|
||||
let imageHeight: number;
|
||||
$: imageHeight = windowHeight - bottomHeight * 0.3;
|
||||
$: imageHeight = isModal && topHeight ? topHeight : windowHeight - bottomHeight * 0.3;
|
||||
|
||||
function getBackdropUri(uris: string[]) {
|
||||
return uris[Math.max(2, Math.floor(uris.length / 8))] || uris[uris.length - 1] || '';
|
||||
@@ -31,7 +40,10 @@
|
||||
"'); height: " +
|
||||
imageHeight.toFixed() +
|
||||
'px'}
|
||||
class="hidden sm:block fixed inset-x-0 bg-center bg-cover z-[-10]"
|
||||
class={classNames('hidden sm:block inset-x-0 bg-center bg-cover z-[-10]', {
|
||||
absolute: isModal,
|
||||
fixed: !isModal
|
||||
})}
|
||||
>
|
||||
<div class="absolute inset-0 bg-darken" />
|
||||
</div>
|
||||
@@ -48,8 +60,32 @@
|
||||
<div class="absolute inset-0 bg-darken" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col h-[85vh] sm:h-screen" transition:fade>
|
||||
<div class="flex-1 relative flex pt-24 px-4 sm:px-8 pb-6">
|
||||
<div
|
||||
class={classNames('flex flex-col', {
|
||||
'h-[85vh] sm:h-screen': !isModal,
|
||||
'': isModal
|
||||
})}
|
||||
transition:fade
|
||||
>
|
||||
<div
|
||||
class={classNames('flex-1 relative flex pt-24 px-4 sm:px-8 pb-6', {
|
||||
'min-h-[60vh]': isModal
|
||||
})}
|
||||
bind:clientHeight={topHeight}
|
||||
>
|
||||
{#if isModal}
|
||||
<a href={`/${type}/${tmdbId}`} class="absolute top-8 right-4 sm:right-8 z-10">
|
||||
<IconButton>
|
||||
<ExternalLink size={20} />
|
||||
</IconButton>
|
||||
</a>
|
||||
<div class="sm:hidden absolute top-8 left-4 sm:left-8 z-10">
|
||||
<button class="flex items-center" on:click={handleCloseModal}>
|
||||
<ChevronLeft size={20} />
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-stone-950 to-30%" />
|
||||
<div class="z-[1] flex-1 flex justify-end gap-8 items-end max-w-screen-2xl mx-auto">
|
||||
<div
|
||||
@@ -86,7 +122,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-6 bg-stone-950 px-2 sm:px-4 lg:px-8 2xl:px-0 pb-6">
|
||||
<div
|
||||
class={classNames('flex flex-col gap-6 bg-stone-950 px-2 sm:px-4 lg:px-8 pb-6', {
|
||||
'2xl:px-0': !isModal
|
||||
})}
|
||||
>
|
||||
<div
|
||||
class="grid grid-cols-4 sm:grid-cols-6 gap-4 sm:gap-x-8 rounded-xl max-w-screen-2xl 2xl:mx-auto py-4"
|
||||
>
|
||||
44
src/lib/components/TitlePageLayout/TitlePageModal.svelte
Normal file
44
src/lib/components/TitlePageLayout/TitlePageModal.svelte
Normal file
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { fly } from 'svelte/transition';
|
||||
import MoviePage from '../../../routes/movie/[id]/MoviePage.svelte';
|
||||
import SeriesPage from '../../../routes/series/[id]/SeriesPage.svelte';
|
||||
import { createModalProps } from '../Modal/Modal';
|
||||
import Modal from '../Modal/Modal.svelte';
|
||||
import { titlePageModal } from './TitlePageModal';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const modalProps = createModalProps(() => {
|
||||
titlePageModal.close();
|
||||
});
|
||||
|
||||
function handleCloseModal() {
|
||||
modalProps.close();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
console.log('modal mounted');
|
||||
|
||||
return () => modalProps.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if $titlePageModal.tmdbId}
|
||||
{@const tmdbId = $titlePageModal.tmdbId}
|
||||
<Modal {...modalProps}>
|
||||
<div
|
||||
class="max-w-screen-2xl overflow-x-hidden overflow-y-scroll h-screen sm:mx-4 lg:mx-12 xl:mx-16 scrollbar-hide"
|
||||
>
|
||||
<div
|
||||
class="relative overflow-hidden"
|
||||
in:fly|global={{ y: 20, duration: 200, delay: 200 }}
|
||||
out:fly|global={{ y: 20, duration: 200 }}
|
||||
>
|
||||
{#if $titlePageModal.type === 'movie'}
|
||||
<MoviePage {tmdbId} isModal={true} {handleCloseModal} />
|
||||
{:else}
|
||||
<SeriesPage {tmdbId} isModal={true} {handleCloseModal} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
{/if}
|
||||
18
src/lib/components/TitlePageLayout/TitlePageModal.ts
Normal file
18
src/lib/components/TitlePageLayout/TitlePageModal.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
type Type = 'movie' | 'series' | undefined;
|
||||
|
||||
function createTitlePageModalStore() {
|
||||
const store = writable<{ tmdbId: number | undefined; type: Type }>({
|
||||
tmdbId: undefined,
|
||||
type: undefined
|
||||
});
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
set: (tmdbId: number | undefined, type: Type) => store.set({ tmdbId, type }),
|
||||
close: () => store.set({ tmdbId: undefined, type: undefined })
|
||||
};
|
||||
}
|
||||
|
||||
export let titlePageModal = createTitlePageModalStore();
|
||||
Reference in New Issue
Block a user