-
-
Notifications
You must be signed in to change notification settings - Fork 150
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: allow users to send gifs (via Tenor API) (#775)
* feat: DocType 'Tenor Credentials' for API creds * build: add '@tiptap/extension-image' extension * chore: configure tiptap Image extension * chore: types gen * feat: GIF Picker * fix: delete 'Tenor credential' DocType * feat: added Tenor API key to Raven Settings * chore: remove trailing whitespace --------- Co-authored-by: Nikhil Kothari <nik.kothari22@live.com>
- Loading branch information
1 parent
ffaa231
commit a53379d
Showing
16 changed files
with
336 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
raven-app/src/components/common/GIFPicker/GIFFeaturedResults.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { useSWR } from "frappe-react-sdk" | ||
import { TENOR_API_KEY, TENOR_CLIENT_KEY, TENOR_FEATURED_API_ENDPOINT_BASE } from "./GIFPicker" | ||
import { GIFGallerySkeleton } from "./GIFGallerySkeleton" | ||
|
||
export interface Props { | ||
onSelect: (gif: Result) => void | ||
} | ||
|
||
const fetcher = (url: string) => fetch(url).then(res => res.json()) | ||
|
||
export const GIFFeaturedResults = ({ onSelect }: Props) => { | ||
|
||
const { data: GIFS, isLoading } = useSWR<TenorResultObject>(`${TENOR_FEATURED_API_ENDPOINT_BASE}?&key=${TENOR_API_KEY}&client_key=${TENOR_CLIENT_KEY}`, fetcher) | ||
|
||
return ( | ||
<div className="overflow-y-auto h-[455px] w-[420px]"> | ||
{isLoading ? <GIFGallerySkeleton /> : | ||
<div className="w-full columns-2 gap-2"> | ||
{GIFS && GIFS.results.map((gif, index) => ( | ||
<div key={index} className="animate-fadein" onClick={() => onSelect(gif)}> | ||
<img className="h-full w-full rounded-sm bg-slate-6" src={gif.media_formats.tinygif.url} alt={gif.title} /> | ||
</div> | ||
))} | ||
</div> | ||
} | ||
</div> | ||
) | ||
} |
48 changes: 48 additions & 0 deletions
48
raven-app/src/components/common/GIFPicker/GIFGallerySkeleton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { Skeleton } from "../Skeleton" | ||
|
||
export const GIFGallerySkeleton = () => { | ||
return ( | ||
<div className="grid grid-cols-2 gap-2"> | ||
<div className="grid gap-2"> | ||
<Skeleton | ||
style={{ height: '170px' }} | ||
className="rounded-sm" /> | ||
<Skeleton | ||
style={{ height: '220px' }} | ||
className="rounded-sm" /> | ||
<Skeleton style={{ height: '120px' }} | ||
className="rounded-sm" /> | ||
</div> | ||
<div className="grid gap-2"> | ||
<Skeleton | ||
style={{ height: '220px' }} | ||
className="rounded-sm" /> | ||
<Skeleton style={{ height: '120px' }} | ||
className="rounded-sm" /> | ||
<Skeleton | ||
style={{ height: '170px' }} | ||
className="rounded-sm" /> | ||
</div> | ||
<div className="grid gap-2"> | ||
<Skeleton | ||
style={{ height: '170px' }} | ||
className="rounded-sm" /> | ||
<Skeleton | ||
style={{ height: '220px' }} | ||
className="rounded-sm" /> | ||
<Skeleton style={{ height: '120px' }} | ||
className="rounded-sm" /> | ||
</div> | ||
<div className="grid gap-2"> | ||
<Skeleton | ||
style={{ height: '220px' }} | ||
className="rounded-sm" /> | ||
<Skeleton | ||
style={{ height: '170px' }} | ||
className="rounded-sm" /> | ||
<Skeleton style={{ height: '120px' }} | ||
className="rounded-sm" /> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { useDebounce } from "@/hooks/useDebounce" | ||
import { Box, Flex, TextField } from "@radix-ui/themes" | ||
import { useState } from "react" | ||
import { BiSearch } from "react-icons/bi" | ||
import { GIFSearchResults } from "./GIFSearchResults" | ||
import { GIFFeaturedResults } from "./GIFFeaturedResults" | ||
|
||
export const TENOR_SEARCH_API_ENDPOINT_BASE = `https://tenor.googleapis.com/v2/search` | ||
export const TENOR_FEATURED_API_ENDPOINT_BASE = `https://tenor.googleapis.com/v2/featured` | ||
// @ts-expect-error | ||
export const TENOR_API_KEY = window.frappe?.boot.tenor_api_key | ||
// @ts-expect-error | ||
export const TENOR_CLIENT_KEY = import.meta.env.DEV ? `dev::${window.frappe?.boot.sitename}` : window.frappe?.boot.sitename | ||
|
||
export interface GIFPickerProps { | ||
onSelect: (gif: any) => void | ||
} | ||
|
||
/** | ||
* GIF Picker component (in-house) to search and select GIFs | ||
* @param onSelect - callback function to handle GIF selection | ||
*/ | ||
export const GIFPicker = ({ onSelect }: GIFPickerProps) => { | ||
// Get GIFs from Tenor API and display them | ||
// show a search bar to search for GIFs | ||
// on select, call onSelect with the gif URL | ||
|
||
const [searchText, setSearchText] = useState("") | ||
const debouncedText = useDebounce(searchText, 200) | ||
|
||
return ( | ||
<Flex className="h-[550px] w-[450px] justify-center"> | ||
<Flex direction={'column'} gap='2' align='center' pt={'3'}> | ||
<Box> | ||
<TextField.Root className="w-[425px] mb-1"> | ||
<TextField.Slot> | ||
<BiSearch /> | ||
</TextField.Slot> | ||
<TextField.Input | ||
onChange={(e) => setSearchText(e.target.value)} | ||
value={searchText} | ||
type='text' | ||
placeholder='Search GIFs' /> | ||
</TextField.Root> | ||
</Box> | ||
|
||
{debouncedText.length >= 2 ? ( | ||
<GIFSearchResults query={debouncedText} onSelect={onSelect} /> | ||
) : ( | ||
<GIFFeaturedResults onSelect={onSelect} /> | ||
)} | ||
|
||
<Box position={'fixed'} className="bottom-0 pb-2 bg-inherit"> | ||
<img | ||
src="https://www.gstatic.com/tenor/web/attribution/PB_tenor_logo_blue_horizontal.png" | ||
alt="Powered by Tenor" | ||
width="100" | ||
/> | ||
</Box> | ||
</Flex> | ||
</Flex> | ||
) | ||
} |
29 changes: 29 additions & 0 deletions
29
raven-app/src/components/common/GIFPicker/GIFSearchResults.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { useSWR } from "frappe-react-sdk" | ||
import { TENOR_API_KEY, TENOR_CLIENT_KEY, TENOR_SEARCH_API_ENDPOINT_BASE } from "./GIFPicker" | ||
import { GIFGallerySkeleton } from "./GIFGallerySkeleton" | ||
|
||
export interface Props { | ||
query: string | ||
onSelect: (gif: Result) => void | ||
} | ||
|
||
const fetcher = (url: string) => fetch(url).then(res => res.json()) | ||
|
||
export const GIFSearchResults = ({ query, onSelect }: Props) => { | ||
|
||
const { data: GIFS, isLoading } = useSWR<TenorResultObject>(`${TENOR_SEARCH_API_ENDPOINT_BASE}?q=${query}&key=${TENOR_API_KEY}&client_key=${TENOR_CLIENT_KEY}&limit=10`, fetcher) | ||
|
||
return ( | ||
<div className="overflow-y-auto h-[455px] w-[420px]"> | ||
{isLoading ? <GIFGallerySkeleton /> : | ||
<div className="w-full columns-2 gap-2"> | ||
{GIFS && GIFS.results.map((gif, index) => ( | ||
<div key={index} className="animate-fadein" onClick={() => onSelect(gif)}> | ||
<img className="h-full w-full rounded-sm bg-slate-6" src={gif.media_formats.tinygif.url} alt={gif.title} /> | ||
</div> | ||
))} | ||
</div> | ||
} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
interface TenorResultObject { | ||
results: Result[]; | ||
next: string; | ||
} | ||
interface Result { | ||
id: string; | ||
title: string; | ||
media_formats: Mediaformats; | ||
created: number; | ||
content_description: string; | ||
itemurl: string; | ||
url: string; | ||
tags: string[]; | ||
flags: any[]; | ||
hasaudio: boolean; | ||
} | ||
interface Mediaformats { | ||
GIFFormatObject: GIFFormatObject; | ||
mediumgif: GIFFormatObject; | ||
tinywebp_transparent?: GIFFormatObject; | ||
tinywebm: GIFFormatObject; | ||
gif: GIFFormatObject; | ||
tinygif: GIFFormatObject; | ||
mp4: GIFFormatObject; | ||
nanomp4: GIFFormatObject; | ||
gifpreview: GIFFormatObject; | ||
webm: GIFFormatObject; | ||
nanowebp_transparent?: GIFFormatObject; | ||
nanowebm: GIFFormatObject; | ||
loopedmp4: GIFFormatObject; | ||
tinygifpreview: GIFFormatObject; | ||
nanowebppreview_transparent?: GIFFormatObject; | ||
tinymp4: GIFFormatObject; | ||
nanogif: GIFFormatObject; | ||
webp_transparent?: GIFFormatObject; | ||
webppreview_transparent?: GIFFormatObject; | ||
tinywebppreview_transparent?: GIFFormatObject; | ||
tinygif_transparent?: GIFFormatObject; | ||
gif_transparent?: GIFFormatObject; | ||
nanogif_transparent?: GIFFormatObject; | ||
} | ||
interface GIFFormatObject { | ||
url: string; | ||
duration: number; | ||
preview: string; | ||
dims: number[]; | ||
size: number; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.