Skip to content

Commit

Permalink
Custom component gallery (#6477)
Browse files Browse the repository at this point in the history
* initial commit

* First draft

* add changeset

* better close button

* Improve card

* Switch

* Add code

* Add code

* Use icons folder + snake_case

* lockfile fix

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
  • Loading branch information
freddyaboulton and gradio-pr-bot committed Dec 11, 2023
1 parent 4d1cbbc commit 21ce721
Show file tree
Hide file tree
Showing 6 changed files with 392 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/short-gifts-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"website": minor
---

feat:Custom component gallery
19 changes: 19 additions & 0 deletions js/_website/src/lib/icons/Close.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<svg
width="100%"
height="100%"
viewBox="0 0 5 5"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve"
style="fill:currentColor;fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"
>
<g>
<path
d="M3.789,0.09C3.903,-0.024 4.088,-0.024 4.202,0.09L4.817,0.705C4.931,0.819 4.931,1.004 4.817,1.118L1.118,4.817C1.004,4.931 0.819,4.931 0.705,4.817L0.09,4.202C-0.024,4.088 -0.024,3.903 0.09,3.789L3.789,0.09Z"
/>
<path
d="M4.825,3.797C4.934,3.907 4.934,4.084 4.825,4.193L4.193,4.825C4.084,4.934 3.907,4.934 3.797,4.825L0.082,1.11C-0.027,1.001 -0.027,0.823 0.082,0.714L0.714,0.082C0.823,-0.027 1.001,-0.027 1.11,0.082L4.825,3.797Z"
/>
</g>
</svg>
20 changes: 20 additions & 0 deletions js/_website/src/lib/icons/CopyButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script lang="ts">
import { svgCopy, svgCheck } from "$lib/assets/copy.js";
export let content: string = "";
let copied = false;
async function copy() {
await navigator.clipboard.writeText(content);
copied = true;
setTimeout(() => (copied = false), 2000);
}
</script>

<button on:click={copy} role="button" tabindex={0}>
{#if !copied}
{@html svgCopy}
{:else}
{@html svgCheck}
{/if}
</button>
160 changes: 160 additions & 0 deletions js/_website/src/routes/custom_components/gallery/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<script lang="ts">
import { onMount, tick } from "svelte";
import type { ComponentData } from "./utils";
import { getRandomIntInclusive, classToEmojiMapping } from "./utils";
import Card from "./Card.svelte";
import Close from "$lib/icons/Close.svelte";
const API = "https://gradio-custom-component-gallery-backend.hf.space/";
const OFFSET = 0;
const LIMIT = 50;
let components: ComponentData[] = [];
let selection: string = "";
let selected_component: ComponentData | null = null;
const COLOR_SETS = [
["from-green-100", "to-green-50"],
["from-yellow-100", "to-yellow-50"],
["from-red-100", "to-red-50"],
["from-blue-100", "to-blue-50"],
["from-pink-100", "to-pink-50"],
["from-purple-100", "to-purple-50"],
["from-green-100", "to-green-50"],
["from-yellow-100", "to-yellow-50"],
["from-red-100", "to-red-50"],
["from-blue-100", "to-blue-50"],
["from-pink-100", "to-pink-50"],
["from-purple-100", "to-purple-50"]
];
function random_color(): string {
const color = COLOR_SETS[getRandomIntInclusive(0, COLOR_SETS.length - 1)];
return `${color[0]} ${color[1]}`;
}
const handle_box_click = (component: ComponentData) => {
selected_component = component;
};
async function fetch_components(selection: string[] = []) {
components = await fetch(
`${API}components?offset=${OFFSET}&limit=${LIMIT}&name_or_tags=${selection.join(
","
)}`
)
.then((response) => response.json())
.catch((error) => `Error: ${error}`);
components.map((x) => (x.background_color = random_color()));
}
onMount(fetch_components);
async function handle_keypress(e: KeyboardEvent): Promise<void> {
await tick();
if (e.key === "Enter") {
e.preventDefault();
fetch_components(selection.split(","));
}
}
</script>

<div class="flex flex-col relative h-full">
<input
type="text"
class="m-8 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="Search component names, keywords and descriptions. Separate multiple keywords with commas and press Enter."
autocomplete="off"
on:keypress={handle_keypress}
bind:value={selection}
/>
<div class="grid relative">
{#each components as component (component.id)}
<div
on:click={() => handle_box_click(component)}
class="box h-36 group font:thin relative rounded-xl shadow-sm hover:shadow-alternate transition-shadow bg-gradient-to-r {component.background_color}"
>
<div class="absolute opacity-30 text-6xl mb-1">
{classToEmojiMapping[component.template] || ""}
</div>
<h2
class="group-hover:underline font-md text-black font-bold max-w-full truncate text-center"
>
{component.name}
</h2>
<span
class="font-sm text-gray-600 text-end max-w-full truncate"
style="position:absolute; bottom:0;"
>
Tags: {component.tags.split(",").join(", ")}</span
>
</div>
{/each}
</div>
</div>
{#if selected_component}
<div class="details-panel open">
<button
class="absolute right-3 top-3 w-4"
on:click={() => (selected_component = null)}
>
<Close />
</button>
<div>
<p class="text-4xl text-black text-center font-bold">
{selected_component.name}
</p>
</div>
<Card data={selected_component}></Card>
</div>
{/if}

<style>
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
margin: 16px;
}
.close-button {
position: absolute;
top: 0;
right: 5;
width: var(--size-1);
color: var(--body-text-color);
}
.box {
border: 1px solid #ddd;
padding: 16px;
cursor: pointer;
display: flex;
position: relative;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: left;
}
.details-panel {
overflow-y: scroll;
position: fixed;
top: 0;
right: 0;
height: 100%;
width: 80%;
background-color: white;
box-shadow: -4px 0 4px rgba(0, 0, 0, 0.1);
z-index: 1000;
transition: transform 0.3s ease-out;
transform: translateX(100%);
display: flex;
flex-direction: column;
}
.details-panel.open {
transform: translateX(0);
}
</style>
145 changes: 145 additions & 0 deletions js/_website/src/routes/custom_components/gallery/Card.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<script lang="ts">
import type { ComponentData } from "./utils";
import CopyButton from "$lib/icons/CopyButton.svelte";
import { BaseCode } from "@gradio/code";
import { onMount } from "svelte";
export let data: ComponentData;
let source_code_link = `https://huggingface.co/spaces/${data.id}`;
let author_link = `https://huggingface.co/${data.author}`;
let discussion_link = `https://huggingface.co/spaces/${data.id}/discussions/new`;
let code_url = `https://huggingface.co/spaces/${data.id}/raw/main/app.py`;
const tabs = ["Demo", "Code"];
let active_tab = 0;
let code: string;
onMount(async () => {
code = await fetch(code_url).then((res) => res.text());
});
</script>

<div class="card space-y-2 bg-gradient-to-r {data.background_color}">
<h1 class="text-xl font-black">{data.description}</h1>
<div class="flex flex-row">
<div class="ml-2">
<code class="code text-md">
pip install {data.name.replace("_", "-")}
<CopyButton content={`pip install ${data.name.replace("_", "-")}`} />
</code>
</div>
<div class="ml-2">
<strong>🖋️ Author</strong>
<a
href={author_link}
target="_blank"
class="link text-orange-400 font-bold">{data.author}</a
>
</div>
<div class="ml-2">
<strong>📑 Template</strong>
{data.template}
</div>
<div class="ml-2">
<strong>🔢 Version:</strong>
{data.version}
</div>
<div class="ml-2">
<strong>🧬</strong>
<a
href={source_code_link}
target="_blank"
class="link text-orange-400 font-bold">code</a
>
</div>
</div>
<div class="flex flex-row">
<div class="ml-2">
<strong>🔖 Tags:</strong>
{data.tags.split(",").join(", ")}
</div>
</div>
<div class="flex flex-row">
<div class="ml-2">
<strong>🤝 Feedback? Stuck?</strong>
<a
href={discussion_link}
target="_blank"
class="link text-orange-400 font-bold"
>
Ask for help</a
>
</div>
</div>
<div class="flex flex-col relative h-full">
<div class="tabs flex flex-row relative">
{#each tabs as tab, index}
<div
class="tab bg-white {active_tab === index
? 'active'
: ''} {active_tab === index
? ''
: 'border-b-2'} px-8 py-1 rounded-t-md border-x-2 border-t-2 border-orange-300 content-center"
on:click={() => {
active_tab = index;
}}
>
{tab}
</div>
{/each}
</div>
{#if active_tab === 0}
<iframe
src={`https://${data.subdomain}.hf.space?__theme=light`}
height="100%"
width="100%"
></iframe>
{:else}
<div class="bg-white">
<BaseCode value={code} language="python" dark_mode={false} />
</div>
{/if}
</div>
</div>

<style>
.card {
border: 1px solid #ccc;
border-radius: 8px;
padding: 16px;
margin: 16px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
overflow-y: auto;
height: 100%;
}
.card p {
margin: 8px 0;
color: rgb(206 100 0 / var(--tw-text-opacity));
}
.link {
text-decoration: underline;
}
.code {
font-family: monospace;
background-color: #f8f9fa;
padding: 0.2em 0.4em;
border-radius: 4px;
}
.tabs {
display: flex;
margin-bottom: -1px;
}
.tab {
cursor: pointer;
}
.active {
font-weight: bold;
}
</style>
43 changes: 43 additions & 0 deletions js/_website/src/routes/custom_components/gallery/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export type ComponentData = {
id: string;
name: string;
template: string;
author: string;
description: string;
tags: string;
version: string;
subdomain: string;
background_color: string;
};

export function getRandomIntInclusive(min: number, max: number): number {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min); // The maximum is inclusive and the minimum is inclusive
}

export const classToEmojiMapping: { [key: string]: string } = {
AnnotatedImage: "🖼️",
Audio: "🔊",
Plot: "📈",
Button: "🔘",
Chatbot: "🤖",
Code: "💻",
ColorPicker: "🎨",
Dataframe: "📊",
Dataset: "📚",
Fallback: "🔄",
File: "📄",
FileExplorer: "📂",
Gallery: "🎨",
HighlightedText: "✨",
HTML: "🔗",
Image: "🖼️",
JSON: "📝",
Label: "🏷️",
Markdown: "📝",
Model3D: "🗿",
State: "🔢",
UploadButton: "📤",
Video: "🎥"
};

0 comments on commit 21ce721

Please sign in to comment.