|
<script lang="ts"> |
|
import MetaTags from "$lib/components/MetaTags.svelte"; |
|
import { page } from "$app/stores"; |
|
import { onMount, tick } from "svelte"; |
|
import type { ComponentData } from "./utils"; |
|
import { clickOutside } from "./utils"; |
|
|
|
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", |
|
"from-green-50 via-green-100 to-green-50", |
|
"from-yellow-50 via-yellow-100 to-yellow-50", |
|
"from-pink-50 via-pink-100 to-pink-50", |
|
"from-blue-50 via-blue-100 to-blue-50", |
|
"from-purple-50 via-purple-100 to-purple-50" |
|
]; |
|
|
|
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"]); |
|
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); |
|
}); |
|
} |
|
</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 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 mb-0 mx-auto w-fit text-sm"> |
|
Search through {components_length} components by name, keyword or description. |
|
<a |
|
class="link text-gray-600" |
|
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 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 p-1 rounded-md text-gray-700 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 |
|
> |
|
<p class="">{component.likes ? component.likes : ""}</p> |
|
</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 p-1 rounded-md text-gray-700"> |
|
@{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 p-1 rounded-md text-gray-700"> |
|
{component.template} |
|
</span> |
|
</p> |
|
{/if} |
|
</div> |
|
{/each} |
|
</div> |
|
</div> |
|
|
|
{#each components as component (component.id)} |
|
<div |
|
class="details-panel open border border-gray-200 shadow-xl rounded-xl bg-white p-5" |
|
class:hidden={!(selected_component == component)} |
|
class:flex={selected_component == component} |
|
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 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 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 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=light`} |
|
height="100%" |
|
width="100%" |
|
></iframe> |
|
</div> |
|
{/each} |
|
|
|
<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> |
|
|