mirror of
https://github.com/aleksilassila/reiverr.git
synced 2026-04-22 08:45:13 +02:00
Added "Dynamic modals"; fixed title modals not allowing child modals
This commit is contained in:
@@ -5,12 +5,12 @@
|
||||
import TitleSearchModal from './TitleSearchModal.svelte';
|
||||
import IconButton from '../IconButton.svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { modalStack } from '../Modal/Modal';
|
||||
|
||||
let y = 0;
|
||||
let transparent = true;
|
||||
let baseStyle = '';
|
||||
|
||||
let isSearchVisible = false;
|
||||
let isMobileMenuVisible = false;
|
||||
|
||||
function getLinkStyle(path: string) {
|
||||
@@ -20,7 +20,17 @@
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: on mobile don't act sticky
|
||||
function openSearchModal() {
|
||||
modalStack.create(TitleSearchModal, {});
|
||||
}
|
||||
|
||||
function handleShortcuts(event: KeyboardEvent) {
|
||||
if (event.key === 'k' && (event.metaKey || event.ctrlKey)) {
|
||||
event.preventDefault();
|
||||
openSearchModal();
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
transparent = y <= 0;
|
||||
baseStyle = classNames(
|
||||
@@ -35,7 +45,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window bind:scrollY={y} />
|
||||
<svelte:window bind:scrollY={y} on:keydown={handleShortcuts} />
|
||||
|
||||
<div class={classNames(baseStyle, 'hidden sm:grid')}>
|
||||
<a
|
||||
@@ -55,7 +65,7 @@
|
||||
<a href="/settings" class={$page && getLinkStyle('/settings')}>Settings</a>
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<IconButton on:click={() => (isSearchVisible = true)}>
|
||||
<IconButton on:click={openSearchModal}>
|
||||
<MagnifyingGlass size={20} />
|
||||
</IconButton>
|
||||
<IconButton>
|
||||
@@ -71,7 +81,7 @@
|
||||
</a>
|
||||
<div />
|
||||
<div class="flex items-center gap-2">
|
||||
<IconButton on:click={() => (isSearchVisible = true)}>
|
||||
<IconButton on:click={openSearchModal}>
|
||||
<MagnifyingGlass size={20} />
|
||||
</IconButton>
|
||||
|
||||
@@ -119,5 +129,3 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<TitleSearchModal bind:visible={isSearchVisible} />
|
||||
|
||||
@@ -3,20 +3,22 @@
|
||||
import { searchTmdbTitles } from '$lib/apis/tmdb/tmdbApi';
|
||||
import { TMDB_POSTER_SMALL } from '$lib/constants';
|
||||
import { MagnifyingGlass } from 'radix-icons-svelte';
|
||||
import { createModalProps } from '../Modal/Modal';
|
||||
import Modal from '../Modal/Modal.svelte';
|
||||
import { modalStack } from '../Modal/Modal';
|
||||
import ModalContent from '../Modal/ModalContainer.svelte';
|
||||
import ModalHeader from '../Modal/ModalHeader.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import type { TitleType } from '$lib/types';
|
||||
|
||||
export let modalId: Symbol;
|
||||
|
||||
export let visible = false;
|
||||
let inputElement: HTMLInputElement;
|
||||
let inputValue = '';
|
||||
let inputElement: HTMLInputElement;
|
||||
|
||||
let fetching = false;
|
||||
let resultProps:
|
||||
| {
|
||||
tmdbId: number;
|
||||
type: 'movie' | 'series';
|
||||
type: TitleType;
|
||||
overview: string;
|
||||
posterUri: string;
|
||||
title: string;
|
||||
@@ -25,19 +27,8 @@
|
||||
}[]
|
||||
| undefined = undefined;
|
||||
|
||||
const modalProps = createModalProps(() => {
|
||||
visible = false;
|
||||
});
|
||||
|
||||
let searchTimeout: NodeJS.Timeout;
|
||||
|
||||
function clear() {
|
||||
inputValue = '';
|
||||
fetching = false;
|
||||
resultProps = undefined;
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
|
||||
const handleInput = () => {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
@@ -53,7 +44,7 @@
|
||||
.filter((i) => i.media_type !== 'person')
|
||||
.filter((i: TmdbMovie2 & TmdbSeries2) => i.release_date || i.first_air_date)
|
||||
.map((i: TmdbMovie2 & TmdbSeries2) => ({
|
||||
// Types not accurate!
|
||||
// ^ Types not accurate! ^
|
||||
tmdbId: i.id || 0,
|
||||
type: (i as any).media_type === 'movie' ? 'movie' : 'series',
|
||||
posterUri: i.poster_path || '',
|
||||
@@ -66,72 +57,59 @@
|
||||
.finally(() => (fetching = false));
|
||||
};
|
||||
|
||||
$: if (visible && inputElement) inputElement.focus();
|
||||
|
||||
function handleShortcuts(event: KeyboardEvent) {
|
||||
if (event.key === 'k' && (event.metaKey || event.ctrlKey)) {
|
||||
event.preventDefault();
|
||||
visible = true;
|
||||
} else if (event.key === 'Escape' && visible) {
|
||||
clear();
|
||||
modalProps.close();
|
||||
}
|
||||
function handleClose() {
|
||||
modalStack.close(modalId);
|
||||
}
|
||||
|
||||
$: if (inputElement) inputElement.focus();
|
||||
|
||||
onMount(() => () => clearTimeout(searchTimeout));
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleShortcuts} />
|
||||
|
||||
{#if visible}
|
||||
<Modal {...modalProps}>
|
||||
<ModalContent>
|
||||
<ModalHeader {...modalProps}>
|
||||
<MagnifyingGlass size={20} class="text-zinc-400" />
|
||||
<input
|
||||
bind:value={inputValue}
|
||||
bind:this={inputElement}
|
||||
on:input={handleInput}
|
||||
type="text"
|
||||
class="flex-1 bg-transparent font-light outline-none"
|
||||
placeholder="Search for Movies and Shows..."
|
||||
/>
|
||||
</ModalHeader>
|
||||
{#if resultProps === undefined || inputValue === ''}
|
||||
<div class="text-sm text-zinc-200 opacity-50 font-light p-4">No recent searches</div>
|
||||
{:else if resultProps?.length === 0 && !fetching}
|
||||
<div class="text-sm text-zinc-200 opacity-50 font-light p-4">No search results</div>
|
||||
{:else}
|
||||
<div class="py-2">
|
||||
{#each resultProps.slice(0, 5) as result}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<a
|
||||
class="flex px-4 py-2 gap-4 hover:bg-lighten focus-visible:bg-lighten cursor-pointer outline-none"
|
||||
href={`/${result.type}/${result.tmdbId}`}
|
||||
on:click={() => {
|
||||
modalProps.close();
|
||||
clear();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={"background-image: url('" + TMDB_POSTER_SMALL + result.posterUri + "');"}
|
||||
class="bg-center bg-cover w-16 h-24 rounded-sm"
|
||||
/>
|
||||
<div class="flex-1 flex flex-col gap-1">
|
||||
<div class="flex gap-2">
|
||||
<div class="font-normal tracking-wide">{result.title}</div>
|
||||
<div class="text-zinc-400">
|
||||
{result.year}
|
||||
</div>
|
||||
{#if result.seasons}
|
||||
<div class="text-zinc-400">{result.seasons}</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="text-sm text-zinc-300 line-clamp-3">{result.overview}</div>
|
||||
<ModalContent>
|
||||
<ModalHeader close={handleClose}>
|
||||
<MagnifyingGlass size={20} class="text-zinc-400" />
|
||||
<input
|
||||
bind:value={inputValue}
|
||||
bind:this={inputElement}
|
||||
on:input={handleInput}
|
||||
type="text"
|
||||
class="flex-1 bg-transparent font-light outline-none"
|
||||
placeholder="Search for Movies and Shows..."
|
||||
/>
|
||||
</ModalHeader>
|
||||
{#if resultProps === undefined || inputValue === ''}
|
||||
<div class="text-sm text-zinc-200 opacity-50 font-light p-4">No recent searches</div>
|
||||
{:else if resultProps?.length === 0 && !fetching}
|
||||
<div class="text-sm text-zinc-200 opacity-50 font-light p-4">No search results</div>
|
||||
{:else}
|
||||
<div class="py-2">
|
||||
{#each resultProps.slice(0, 5) as result}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<a
|
||||
class="flex px-4 py-2 gap-4 hover:bg-lighten focus-visible:bg-lighten cursor-pointer outline-none"
|
||||
href={`/${result.type}/${result.tmdbId}`}
|
||||
on:click={handleClose}
|
||||
>
|
||||
<div
|
||||
style={"background-image: url('" + TMDB_POSTER_SMALL + result.posterUri + "');"}
|
||||
class="bg-center bg-cover w-16 h-24 rounded-sm"
|
||||
/>
|
||||
<div class="flex-1 flex flex-col gap-1">
|
||||
<div class="flex gap-2">
|
||||
<div class="font-normal tracking-wide">{result.title}</div>
|
||||
<div class="text-zinc-400">
|
||||
{result.year}
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
{/if}
|
||||
{#if result.seasons}
|
||||
<div class="text-zinc-400">{result.seasons}</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="text-sm text-zinc-300 line-clamp-3">{result.overview}</div>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</ModalContent>
|
||||
|
||||
Reference in New Issue
Block a user