[READ-ONLY] Mirror of https://github.com/flo-bit/skywatched. review movies and tv shows, based on at proto
skywatched.app
4.7 kB
166 lines
1<script lang="ts">
2 import { rateMovieModal } from '$lib/state.svelte';
3 import { createCombobox, melt } from '@melt-ui/svelte';
4 import { fly } from 'svelte/transition';
5
6 const {
7 elements: { menu, input, option, label },
8 states: { open, inputValue, touchedInput },
9 helpers: { isSelected }
10 } = createCombobox({
11 forceVisible: true
12 });
13
14 let debounceTimer: ReturnType<typeof setTimeout>;
15
16 const debounce = (callback: () => void) => {
17 clearTimeout(debounceTimer);
18 debounceTimer = setTimeout(callback, 500);
19 };
20
21 let results: {
22 movieId: number | null;
23 showId: number | null;
24 title: string;
25 poster_path: string;
26 media_type: string;
27 kind: 'movie' | 'show';
28 }[] = [];
29
30 let searching = false;
31
32 function onInput() {
33 if ($inputValue.length < 2) {
34 results = [];
35 } else {
36 searching = true;
37 debounce(async () => {
38 if ($inputValue.length < 2) return;
39
40 const response = await fetch(`/api/search?q=${$inputValue}`);
41 const data = await response.json();
42 results = data
43 .filter(
44 (item: any) =>
45 (item.media_type === 'movie' || item.media_type === 'tv') && item.poster_path
46 )
47 .map((item: any) => ({
48 movieId: item.media_type === 'movie' ? item.id : null,
49 showId: item.media_type === 'tv' ? item.id : null,
50 title: item.title || item.name,
51 poster_path: item.poster_path,
52 media_type: item.media_type,
53 kind: item.media_type === 'movie' ? 'movie' : 'show'
54 }));
55
56 searching = false;
57 });
58 }
59 }
60</script>
61
62<div class="flex flex-col gap-1">
63 <!-- svelte-ignore a11y-label-has-associated-control - $label contains the 'for' attribute -->
64 <!-- <label use:melt={$label}>
65 <span class="text-sm font-medium text-accent-900">Find a movie or show:</span>
66 </label> -->
67
68 <div class="relative">
69 <input
70 use:melt={$input}
71 class="flex h-10 w-full items-center justify-between rounded-lg border-base-800 bg-black px-3 pr-12 text-base-50
72 placeholder:text-base-400 focus:border-none focus:outline-none focus:ring-2 focus:ring-accent-500"
73 placeholder="Find a movie or show"
74 oninput={onInput}
75 />
76 <div class="absolute right-2 top-1/2 z-10 -translate-y-1/2 text-accent-500">
77 {#if $open && $inputValue.length > 1}
78 <svg
79 xmlns="http://www.w3.org/2000/svg"
80 fill="none"
81 viewBox="0 0 24 24"
82 stroke-width="1.5"
83 stroke="currentColor"
84 class="size-4"
85 >
86 <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5" />
87 </svg>
88 {:else}
89 <svg
90 xmlns="http://www.w3.org/2000/svg"
91 fill="none"
92 viewBox="0 0 24 24"
93 stroke-width="1.5"
94 stroke="currentColor"
95 class="size-4"
96 >
97 <path
98 stroke-linecap="round"
99 stroke-linejoin="round"
100 d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
101 />
102 </svg>
103 {/if}
104 </div>
105 </div>
106</div>
107{#if $open && $inputValue.length > 1}
108 <ul
109 class=" z-50 flex max-h-[300px] flex-col overflow-hidden rounded-lg"
110 use:melt={$menu}
111 transition:fly={{ duration: 150, y: -5 }}
112 >
113 <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
114 <div
115 class="mx-2 flex max-h-full flex-col gap-0 divide-y divide-base-700/50 overflow-y-auto rounded-lg border border-base-700 bg-base-800 px-2 py-2 text-base-50"
116 tabindex="0"
117 >
118 {#each results as item, index (index)}
119 <li
120 use:melt={$option({
121 value: item,
122 label: item.title
123 })}
124 class="relative w-full cursor-pointer scroll-my-2 px-2 py-2 data-[highlighted]:text-accent-200"
125 >
126 <button
127 onclick={() => {
128 rateMovieModal.selectedItem = {
129 movieId: item.movieId ?? undefined,
130 showId: item.showId ?? undefined,
131 kind: item.kind,
132 name: item.title,
133 posterPath: item.poster_path,
134 currentRating: undefined,
135 currentReview: undefined
136 };
137 rateMovieModal.showModal = true;
138 }}
139 class="flex items-center justify-start gap-2 w-full"
140 >
141 <div
142 class="relative z-20 aspect-[2/3] h-12 w-auto shrink-0 overflow-hidden rounded-md border border-base-800 bg-base-900/50"
143 >
144 <img
145 src="https://image.tmdb.org/t/p/w154{item.poster_path}"
146 alt="movie poster for {item.title}"
147 class="size-full object-cover object-center lg:size-full"
148 />
149 </div>
150 <span class="text-left text-sm font-medium">{item.title}</span>
151 </button>
152 </li>
153 {:else}
154 <li
155 class="relative cursor-pointer rounded-md py-1 pl-4 pr-4 text-sm data-[highlighted]:text-accent-200"
156 >
157 {#if searching}
158 Searching...
159 {:else if $inputValue.length > 1}
160 No results found
161 {/if}
162 </li>
163 {/each}
164 </div>
165 </ul>
166{/if}