<script lang="ts">
	import MetaTags from "$lib/components/MetaTags.svelte";
	import { page } from "$app/stores";
	import { browser } from "$app/environment";
	import { onMount, tick } from "svelte";
	import type { ComponentData } from "./utils";
	import { clickOutside } from "./utils";
	import { theme } from "$lib/stores/theme";

	const API = "https://gradio-custom-component-gallery-backend.hf.space/";

	let components: ComponentData[] = [];
	let selection: string = "";
	let link_copied = false;

	let selected_component: ComponentData | null = null;
	let components_length: number = 0;

	const COLOR_SETS = [
		"from-red-50 via-red-100 to-red-50 dark:from-red-950 dark:via-red-900 dark:to-red-950",
		"from-green-50 via-green-100 to-green-50 dark:from-green-950 dark:via-green-900 dark:to-green-950",
		"from-yellow-50 via-yellow-100 to-yellow-50 dark:from-yellow-950 dark:via-yellow-900 dark:to-yellow-950",
		"from-pink-50 via-pink-100 to-pink-50 dark:from-pink-950 dark:via-pink-900 dark:to-pink-950",
		"from-blue-50 via-blue-100 to-blue-50 dark:from-blue-950 dark:via-blue-900 dark:to-blue-950",
		"from-purple-50 via-purple-100 to-purple-50 dark:from-purple-950 dark:via-purple-900 dark:to-purple-950"
	];

	let color_mapping: { [key: string]: string } = {};

	function define_colors(components: ComponentData[]) {
		let counter = 0;
		for (const component of components) {
			if (counter >= COLOR_SETS.length) {
				counter = 0;
			}
			if (!(component.id in color_mapping)) {
				color_mapping[component.id] = COLOR_SETS[counter];
			}
			component.background_color = color_mapping[component.id];
			counter++;
		}
	}

	const handle_box_click = (component: ComponentData) => {
		selected_component = component;
	};

	async function fetch_components(selection: string[] = []) {
		components = await fetch(
			`${API}components?name_or_tags=${selection.join(",")}`
		)
			.then((response) => response.json())
			.catch((error) => `Error: ${error}`);
		define_colors(components);
		if (!components_length) {
			components_length = components.length;
		}
		components = components.sort((a, b) => b["likes"] - a["likes"]);
		if (browser) {
			const id = $page.url.searchParams.get("id");
			selected_component =
				components.find((component) => component.id === id) ?? null;
		}
	}

	onMount(fetch_components);

	async function handle_keypress(e: KeyboardEvent): Promise<void> {
		await tick();
		e.preventDefault();
		fetch_components(selection.split(","));
	}

	function copy_link(id: string) {
		const url = $page.url;
		url.searchParams.set("id", id);
		const link = url.toString();
		navigator.clipboard.writeText(link).then(() => {
			window.setTimeout(() => {
				link_copied = false;
			}, 1000);
		});
	}

	$: fetch_components(selection.split(","));
</script>

<MetaTags
	title="Gradio Custom Components Gallery"
	url={$page.url.pathname}
	canonical={$page.url.pathname}
	description="Search through a gallery of custom components."
/>

<div class="container mx-auto px-4 relative pt-8 mb-0">
	<input
		type="text"
		class="w-full border border-gray-200 dark:border-gray-700 dark:bg-neutral-800 dark:text-gray-200 p-1 rounded-md outline-none text-center text-lg mb-1 focus:placeholder-transparent focus:shadow-none focus:border-orange-500 focus:ring-0"
		placeholder="What are you looking for?"
		autocomplete="off"
		on:keyup={handle_keypress}
		bind:value={selection}
	/>
	<div class="text-gray-600 dark:text-gray-300 mb-0 mx-auto w-fit text-sm">
		Search through {components_length} components by name, keyword or description.
		<a
			class="link text-gray-600 dark:text-gray-300"
			href="https://www.gradio.app/guides/five-minute-guide"
			>Read more about Custom Components.</a
		>
	</div>
	<div class="grid grid-cols-1 lg:grid-cols-4 gap-6 !m-0 !mt-8">
		{#each components as component (component.id)}
			<div
				on:click={(event) => {
					handle_box_click(component);
					event.stopPropagation();
				}}
				class=" cursor-pointer px-3 pt-3 h-40 group font:thin relative rounded-xl shadow-sm transform hover:scale-[1.02] hover:shadow-alternate transition bg-gradient-to-tr {component.background_color}"
			>
				<h2
					class="text-md font-semibold text-gray-700 dark:text-gray-200 max-w-full truncate py-1"
				>
					{component.name.startsWith("gradio_")
						? component.name.slice(7)
						: component.name}
				</h2>

				{#if component.likes}
					<p
						class="text-sm font-light py-1"
						style="position: absolute; top: 5%; right: 5%"
					>
						<span
							class="bg-white dark:bg-neutral-800 p-1 rounded-md text-gray-700 dark:text-gray-200 inline-flex align-middle"
						>
							<svg
								class="mr-1 self-center"
								xmlns="http://www.w3.org/2000/svg"
								xmlns:xlink="http://www.w3.org/1999/xlink"
								aria-hidden="true"
								focusable="false"
								role="img"
								width="1em"
								height="1em"
								preserveAspectRatio="xMidYMid meet"
								viewBox="0 0 32 32"
								fill="currentColor"
								><path
									d="M22.45,6a5.47,5.47,0,0,1,3.91,1.64,5.7,5.7,0,0,1,0,8L16,26.13,5.64,15.64a5.7,5.7,0,0,1,0-8,5.48,5.48,0,0,1,7.82,0L16,10.24l2.53-2.58A5.44,5.44,0,0,1,22.45,6m0-2a7.47,7.47,0,0,0-5.34,2.24L16,7.36,14.89,6.24a7.49,7.49,0,0,0-10.68,0,7.72,7.72,0,0,0,0,10.82L16,29,27.79,17.06a7.72,7.72,0,0,0,0-10.82A7.49,7.49,0,0,0,22.45,4Z"
								></path></svg
							>
							<span class="">{component.likes ? component.likes : ""}</span>
						</span>
					</p>
				{/if}
				<p class="description text-md font-light py-1">
					{component.description}
				</p>
				<p
					class="text-sm font-light py-1"
					style="position: absolute; bottom: 5%; left: 5%"
				>
					<span
						class="bg-white dark:bg-neutral-800 p-1 rounded-md text-gray-700 dark:text-gray-200"
					>
						@{component.author}
					</span>
				</p>
				{#if component.template && component.template != "Fallback"}
					<p
						class="text-sm font-light py-1"
						style="position: absolute; bottom: 5%; right: 5%"
					>
						<span
							class="bg-white dark:bg-neutral-800 p-1 rounded-md text-gray-700 dark:text-gray-200"
						>
							{component.template}
						</span>
					</p>
				{/if}
			</div>
		{/each}
	</div>
</div>

{#if selected_component}
	{@const component = selected_component}
	<div
		class="details-panel open border border-gray-200 dark:border-gray-700 shadow-xl rounded-xl bg-white dark:bg-neutral-900 p-5 flex"
		use:clickOutside={() => {
			selected_component = null;
		}}
	>
		<div class="self-end mr-8 flex">
			{#if !link_copied}
				<button
					on:click={(e) => {
						link_copied = true;
						copy_link(component.id);
					}}
					class="rounded-md w-fit px-3.5 py-1 text-sm font-semibold text-white dark:text-gray-900 bg-orange-300 hover:drop-shadow-sm mr-4"
				>
					Share
				</button>
			{:else}
				<span
					class="rounded-md w-fit px-3.5 py-1 text-sm font-semibold text-white dark:text-gray-900 bg-orange-300 hover:drop-shadow-sm mr-4"
				>
					Link copied to clipboard!
				</span>
			{/if}
			<a
				href={`https://huggingface.co/spaces/${component.id}`}
				target="_blank"
				class="rounded-md w-fit px-3.5 py-1 text-sm font-semibold text-white dark:text-gray-900 bg-orange-300 hover:drop-shadow-sm"
			>
				Go to Space <span aria-hidden="true">→</span>
			</a>
		</div>
		<iframe
			src={`https://${component.subdomain}.hf.space?__theme=${$theme}`}
			height="100%"
			width="100%"
			loading="lazy"
		></iframe>
	</div>
{/if}

<style>
	.close-button {
		position: absolute;
		top: 0;
		right: 5;
		width: var(--size-1);
		color: var(--body-text-color);
	}

	.details-panel {
		position: fixed;
		top: 5%;
		right: 5%;
		height: 90%;
		width: 90%;
		z-index: 1000;
		flex-direction: column;
	}

	.description {
		display: -webkit-box;
		-webkit-line-clamp: 2;
		-webkit-box-orient: vertical;
		overflow: hidden;
	}
</style>
