Add GIF select to composer (#3600)

* create dialog with flatlist in it

* use alf for composer photos/camera/gif buttons

* add gif icons

* focus textinput on gif dialog close

* add giphy API + gif grid

* web support

* add consent confirmation

* track gif select

* desktop web consent styles

* use InlineLinkText instead of Link

* add error/loading state

* hide sideborders on web

* disable composer buttons where necessary

* skip cardyb and set thumbnail directly

* switch legacy analytics to statsig

* remove autoplay prop

* disable photo/gif buttons if external media is present

* memoize listmaybeplaceholder

* fix pagination

* don't set `value` of TextInput, clear via ref

* remove console.log

* close modal if press escape

* pass alt text in the description

* Fix typo

* Rm dialog

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
This commit is contained in:
Samuel Newman 2024-04-19 03:42:26 +01:00 committed by GitHub
parent 2090738185
commit ba1c4834ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 907 additions and 106 deletions

View file

@ -1,9 +1,13 @@
import React from 'react'
import * as persisted from '#/state/persisted'
import {EmbedPlayerSource} from 'lib/strings/embed-player'
type StateContext = persisted.Schema['externalEmbeds']
type SetContext = (source: EmbedPlayerSource, value: 'show' | 'hide') => void
type SetContext = (
source: EmbedPlayerSource,
value: 'show' | 'hide' | undefined,
) => void
const stateContext = React.createContext<StateContext>(
persisted.defaults.externalEmbeds,
@ -14,7 +18,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
const [state, setState] = React.useState(persisted.get('externalEmbeds'))
const setStateWrapped = React.useCallback(
(source: EmbedPlayerSource, value: 'show' | 'hide') => {
(source: EmbedPlayerSource, value: 'show' | 'hide' | undefined) => {
setState(prev => {
persisted.write('externalEmbeds', {
...prev,

280
src/state/queries/giphy.ts Normal file
View file

@ -0,0 +1,280 @@
import {keepPreviousData, useInfiniteQuery} from '@tanstack/react-query'
import {GIPHY_API_KEY, GIPHY_API_URL} from '#/lib/constants'
export const RQKEY_ROOT = 'giphy'
export const RQKEY_TRENDING = [RQKEY_ROOT, 'trending']
export const RQKEY_SEARCH = (query: string) => [RQKEY_ROOT, 'search', query]
const getTrendingGifs = createGiphyApi<
{
limit?: number
offset?: number
rating?: string
random_id?: string
bundle?: string
},
{data: Gif[]; pagination: Pagination}
>('/v1/gifs/trending')
const searchGifs = createGiphyApi<
{
q: string
limit?: number
offset?: number
rating?: string
lang?: string
random_id?: string
bundle?: string
},
{data: Gif[]; pagination: Pagination}
>('/v1/gifs/search')
export function useGiphyTrending() {
return useInfiniteQuery({
queryKey: RQKEY_TRENDING,
queryFn: ({pageParam}) => getTrendingGifs({offset: pageParam}),
initialPageParam: 0,
getNextPageParam: lastPage =>
lastPage.pagination.offset + lastPage.pagination.count,
})
}
export function useGifphySearch(query: string) {
return useInfiniteQuery({
queryKey: RQKEY_SEARCH(query),
queryFn: ({pageParam}) => searchGifs({q: query, offset: pageParam}),
initialPageParam: 0,
getNextPageParam: lastPage =>
lastPage.pagination.offset + lastPage.pagination.count,
enabled: !!query,
placeholderData: keepPreviousData,
})
}
function createGiphyApi<Input extends object, Ouput>(
path: string,
): (input: Input) => Promise<
Ouput & {
meta: Meta
}
> {
return async input => {
const url = new URL(path, GIPHY_API_URL)
url.searchParams.set('api_key', GIPHY_API_KEY)
for (const [key, value] of Object.entries(input)) {
url.searchParams.set(key, String(value))
}
const res = await fetch(url.toString(), {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
if (!res.ok) {
throw new Error('Failed to fetch Giphy API')
}
return res.json()
}
}
export type Gif = {
type: string
id: string
slug: string
url: string
bitly_url: string
embed_url: string
username: string
source: string
rating: string
content_url: string
user: User
source_tld: string
source_post_url: string
update_datetime: string
create_datetime: string
import_datetime: string
trending_datetime: string
images: Images
title: string
alt_text: string
}
type Images = {
fixed_height: {
url: string
width: string
height: string
size: string
mp4: string
mp4_size: string
webp: string
webp_size: string
}
fixed_height_still: {
url: string
width: string
height: string
}
fixed_height_downsampled: {
url: string
width: string
height: string
size: string
webp: string
webp_size: string
}
fixed_width: {
url: string
width: string
height: string
size: string
mp4: string
mp4_size: string
webp: string
webp_size: string
}
fixed_width_still: {
url: string
width: string
height: string
}
fixed_width_downsampled: {
url: string
width: string
height: string
size: string
webp: string
webp_size: string
}
fixed_height_small: {
url: string
width: string
height: string
size: string
mp4: string
mp4_size: string
webp: string
webp_size: string
}
fixed_height_small_still: {
url: string
width: string
height: string
}
fixed_width_small: {
url: string
width: string
height: string
size: string
mp4: string
mp4_size: string
webp: string
webp_size: string
}
fixed_width_small_still: {
url: string
width: string
height: string
}
downsized: {
url: string
width: string
height: string
size: string
}
downsized_still: {
url: string
width: string
height: string
}
downsized_large: {
url: string
width: string
height: string
size: string
}
downsized_medium: {
url: string
width: string
height: string
size: string
}
downsized_small: {
mp4: string
width: string
height: string
mp4_size: string
}
original: {
width: string
height: string
size: string
frames: string
mp4: string
mp4_size: string
webp: string
webp_size: string
}
original_still: {
url: string
width: string
height: string
}
looping: {
mp4: string
}
preview: {
mp4: string
mp4_size: string
width: string
height: string
}
preview_gif: {
url: string
width: string
height: string
}
}
type User = {
avatar_url: string
banner_url: string
profile_url: string
username: string
display_name: string
}
type Meta = {
msg: string
status: number
response_id: string
}
type Pagination = {
offset: number
total_count: number
count: number
}