You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
6.3 KiB
206 lines
6.3 KiB
// 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
|