Added "Dynamic modals"; fixed title modals not allowing child modals

This commit is contained in:
Aleksi Lassila
2023-08-09 23:08:46 +03:00
parent a52e96e0e1
commit 25c018174d
24 changed files with 507 additions and 558 deletions

View File

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

View File

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