|
|
// app.tsx
// Copyright (C) 2020 Dwayne Harris
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import React, { FC, useState, useEffect } from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faSearch, faSpinner } from '@fortawesome/free-solid-svg-icons' import { Communicator, Theme, InstallationSettings } from '../../../communicator' import Gif from './gif' import { GiphyGif } from '../../../types'
const giphyIcon = require('./giphy.png') import '../../../styles/default.css'
type APIResponse = GiphyGif[]
interface Props { communicator: Communicator }
const useDebounce = (value: string, delay = 500) => { const [valueD, setValueD] = useState(value)
useEffect(() => { const handler = setTimeout(() => { setValueD(value) }, delay)
return () => { clearTimeout(handler) } }, [value, delay])
return valueD }
const App: FC<Props> = ({ communicator }) => { const [posting, setPosting] = useState(false) const [searching, setSearching] = useState(false) const [search, setSearch] = useState('') const [settings, setSettings] = useState<InstallationSettings>({}) const searchD = useDebounce(search) const [gifs, setGifs] = useState<GiphyGif[]>([]) const [selected, setSelected] = useState('')
const [theme, setTheme] = useState<Theme>({ primary: '#333', primaryAlternate: '#fff', secondary: '#777', backgroundPrimary: '#fff', backgroundSecondary: '#ccc', text: '#555', red: '#ff1a1a', green: '#00802b', blue: '#005ce6', })
useEffect(() => { const init = async () => { try { const content = await communicator.init()
if (content) { if (content.settings) setSettings(content.settings) if (content.theme) setTheme(content.theme)
if (content.parent?.data?.search) { setSearch(content.parent.data.search) } }
await communicator.setHeight(1000) } catch (err) {} } init() }, [])
useEffect(() => { const doTrending = async () => { try { const response = await fetch('/api/gifs/home') setGifs(await response.json() as APIResponse) } catch (err) {} }
const doSearch = async () => { try { setSearching(true)
const response = await fetch(`/api/gifs/search?q=${search}`) setGifs(await response.json() as APIResponse)
const previousSearches: string[] = settings.searches ?? [] const searches = [ search, ...previousSearches.slice(0, 9), ]
const newSettings = { ...settings, searches, }
await communicator.saveSettings(newSettings) setSettings(newSettings) } catch (err) { console.error(err) }
setSearching(false) }
if (!search) { doTrending() } else if (search.length > 2) { doSearch() } }, [searchD])
const handleSearchChange = (search: string) => { setSearch(search) }
const post = async () => { try { const gif = gifs.find(g => g.id === selected) if (!gif) return
setPosting(true)
await communicator.post({ attachments: [{ url: gif.images.fixed_height.url, text: gif.title, }], data: { search, }, visible: true, })
setSearch('') setSelected('') } catch (err) { console.error(err) }
setPosting(false) }
const getPlaceholder = () => { const searches = settings.searches as string[] ?? [] return searches[0] ? `Last Search: ${searches[0]}` : 'Search' }
return ( <div style={{ backgroundColor: theme.backgroundPrimary }}> <div className="field"> <div className="control-container"> <div className="icon" style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }}> <FontAwesomeIcon icon={faSearch} /> </div> <div className="control"> <input style={{ backgroundColor: theme.backgroundSecondary, borderColor: theme.secondary, color: theme.text }} type="text" placeholder={getPlaceholder()} value={search} onChange={(e) => handleSearchChange(e.target.value)} /> </div> </div> </div>
<div className="gifs"> {gifs.map(gif => <Gif key={gif.id} gif={gif} selected={gif.id === selected} onSelect={setSelected} />)} </div>
<nav className="level"> <div> <img src={giphyIcon} style={{ width: 50, height: 18 }} /> </div>
<div> <button style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }} onClick={() => post()} disabled={posting || !selected}> {posting ? <FontAwesomeIcon icon={faSpinner} spin /> : <span>Post</span>} </button> </div> </nav> </div> ) }
export default App
|