[ABANDONDED] Set of "apps" for the Flexor social network.
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

// 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