[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

5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
  1. // app.tsx
  2. // Copyright (C) 2020 Dwayne Harris
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation, either version 3 of the License, or
  6. // (at your option) any later version.
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU General Public License for more details.
  11. // You should have received a copy of the GNU General Public License
  12. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. import React, { FC, useState, useEffect } from 'react'
  14. import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
  15. import { faSearch, faSpinner } from '@fortawesome/free-solid-svg-icons'
  16. import { Communicator, Theme, InstallationSettings } from '../../../communicator'
  17. import Gif from './gif'
  18. import { GiphyGif } from '../../../types'
  19. const giphyIcon = require('./giphy.png')
  20. import '../../../styles/default.css'
  21. type APIResponse = GiphyGif[]
  22. interface Props {
  23. communicator: Communicator
  24. }
  25. const useDebounce = (value: string, delay = 500) => {
  26. const [valueD, setValueD] = useState(value)
  27. useEffect(() => {
  28. const handler = setTimeout(() => {
  29. setValueD(value)
  30. }, delay)
  31. return () => {
  32. clearTimeout(handler)
  33. }
  34. }, [value, delay])
  35. return valueD
  36. }
  37. const App: FC<Props> = ({ communicator }) => {
  38. const [posting, setPosting] = useState(false)
  39. const [searching, setSearching] = useState(false)
  40. const [search, setSearch] = useState('')
  41. const [settings, setSettings] = useState<InstallationSettings>({})
  42. const searchD = useDebounce(search)
  43. const [gifs, setGifs] = useState<GiphyGif[]>([])
  44. const [selected, setSelected] = useState('')
  45. const [theme, setTheme] = useState<Theme>({
  46. primary: '#333',
  47. primaryAlternate: '#fff',
  48. secondary: '#777',
  49. backgroundPrimary: '#fff',
  50. backgroundSecondary: '#ccc',
  51. text: '#555',
  52. red: '#ff1a1a',
  53. green: '#00802b',
  54. blue: '#005ce6',
  55. })
  56. useEffect(() => {
  57. const init = async () => {
  58. try {
  59. const content = await communicator.init()
  60. if (content) {
  61. if (content.settings) setSettings(content.settings)
  62. if (content.theme) setTheme(content.theme)
  63. if (content.parent?.data?.search) {
  64. setSearch(content.parent.data.search)
  65. }
  66. }
  67. await communicator.setHeight(1000)
  68. } catch (err) {}
  69. }
  70. init()
  71. }, [])
  72. useEffect(() => {
  73. const doTrending = async () => {
  74. try {
  75. const response = await fetch('/api/gifs/home')
  76. setGifs(await response.json() as APIResponse)
  77. } catch (err) {}
  78. }
  79. const doSearch = async () => {
  80. try {
  81. setSearching(true)
  82. const response = await fetch(`/api/gifs/search?q=${search}`)
  83. setGifs(await response.json() as APIResponse)
  84. const previousSearches: string[] = settings.searches ?? []
  85. const searches = [
  86. search,
  87. ...previousSearches.slice(0, 9),
  88. ]
  89. const newSettings = {
  90. ...settings,
  91. searches,
  92. }
  93. await communicator.saveSettings(newSettings)
  94. setSettings(newSettings)
  95. } catch (err) {
  96. console.error(err)
  97. }
  98. setSearching(false)
  99. }
  100. if (!search) {
  101. doTrending()
  102. } else if (search.length > 2) {
  103. doSearch()
  104. }
  105. }, [searchD])
  106. const handleSearchChange = (search: string) => {
  107. setSearch(search)
  108. }
  109. const post = async () => {
  110. try {
  111. const gif = gifs.find(g => g.id === selected)
  112. if (!gif) return
  113. setPosting(true)
  114. await communicator.post({
  115. attachments: [{
  116. url: gif.images.fixed_height.url,
  117. text: gif.title,
  118. }],
  119. data: {
  120. search,
  121. },
  122. visible: true,
  123. })
  124. setSearch('')
  125. setSelected('')
  126. } catch (err) {
  127. console.error(err)
  128. }
  129. setPosting(false)
  130. }
  131. const getPlaceholder = () => {
  132. const searches = settings.searches as string[] ?? []
  133. return searches[0] ? `Last Search: ${searches[0]}` : 'Search'
  134. }
  135. return (
  136. <div style={{ backgroundColor: theme.backgroundPrimary }}>
  137. <div className="field">
  138. <div className="control-container">
  139. <div className="icon" style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }}>
  140. <FontAwesomeIcon icon={faSearch} />
  141. </div>
  142. <div className="control">
  143. <input
  144. style={{ backgroundColor: theme.backgroundSecondary, borderColor: theme.secondary, color: theme.text }}
  145. type="text"
  146. placeholder={getPlaceholder()}
  147. value={search}
  148. onChange={(e) => handleSearchChange(e.target.value)} />
  149. </div>
  150. </div>
  151. </div>
  152. <div className="gifs">
  153. {gifs.map(gif => <Gif key={gif.id} gif={gif} selected={gif.id === selected} onSelect={setSelected} />)}
  154. </div>
  155. <nav className="level">
  156. <div>
  157. <img src={giphyIcon} style={{ width: 50, height: 18 }} />
  158. </div>
  159. <div>
  160. <button style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }} onClick={() => post()} disabled={posting || !selected}>
  161. {posting ? <FontAwesomeIcon icon={faSpinner} spin /> : <span>Post</span>}
  162. </button>
  163. </div>
  164. </nav>
  165. </div>
  166. )
  167. }
  168. export default App