[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.

190 lines
5.7 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. import React, { FC, useState, useEffect } from 'react'
  2. import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
  3. import { faSearch, faSpinner } from '@fortawesome/free-solid-svg-icons'
  4. import { Communicator, Theme, InstallationSettings } from '../../../communicator'
  5. import Gif from './gif'
  6. import { GiphyGif } from '../../../types'
  7. const giphyIcon = require('./giphy.png')
  8. import '../../../styles/default.css'
  9. type APIResponse = GiphyGif[]
  10. interface Props {
  11. communicator: Communicator
  12. }
  13. const useDebounce = (value: string, delay = 500) => {
  14. const [valueD, setValueD] = useState(value)
  15. useEffect(() => {
  16. const handler = setTimeout(() => {
  17. setValueD(value)
  18. }, delay)
  19. return () => {
  20. clearTimeout(handler)
  21. }
  22. }, [value, delay])
  23. return valueD
  24. }
  25. const App: FC<Props> = ({ communicator }) => {
  26. const [posting, setPosting] = useState(false)
  27. const [searching, setSearching] = useState(false)
  28. const [search, setSearch] = useState('')
  29. const [settings, setSettings] = useState<InstallationSettings>({})
  30. const searchD = useDebounce(search)
  31. const [gifs, setGifs] = useState<GiphyGif[]>([])
  32. const [selected, setSelected] = useState('')
  33. const [theme, setTheme] = useState<Theme>({
  34. primary: '#333',
  35. primaryAlternate: '#fff',
  36. secondary: '#777',
  37. backgroundPrimary: '#fff',
  38. backgroundSecondary: '#ccc',
  39. text: '#555',
  40. red: '#ff1a1a',
  41. green: '#00802b',
  42. blue: '#005ce6',
  43. })
  44. useEffect(() => {
  45. const init = async () => {
  46. try {
  47. const content = await communicator.init()
  48. if (content) {
  49. if (content.settings) setSettings(content.settings)
  50. if (content.theme) setTheme(content.theme)
  51. if (content.parent && content.parent.data && content.parent.data.search) {
  52. setSearch(content.parent.data.search)
  53. }
  54. }
  55. await communicator.setHeight(1000)
  56. } catch (err) {}
  57. }
  58. init()
  59. }, [])
  60. useEffect(() => {
  61. const doTrending = async () => {
  62. try {
  63. const response = await fetch('/api/gifs/home')
  64. setGifs(await response.json() as APIResponse)
  65. } catch (err) {}
  66. }
  67. const doSearch = async () => {
  68. try {
  69. setSearching(true)
  70. const response = await fetch(`/api/gifs/search?q=${search}`)
  71. setGifs(await response.json() as APIResponse)
  72. const previousSearches: string[] = settings.searches ?? []
  73. const searches = [
  74. search,
  75. ...previousSearches.slice(0, 9),
  76. ]
  77. const newSettings = {
  78. ...settings,
  79. searches,
  80. }
  81. await communicator.saveSettings(newSettings)
  82. setSettings(newSettings)
  83. } catch (err) {
  84. console.error(err)
  85. }
  86. setSearching(false)
  87. }
  88. if (!search) {
  89. doTrending()
  90. } else if (search.length > 2) {
  91. doSearch()
  92. }
  93. }, [searchD])
  94. const handleSearchChange = (search: string) => {
  95. setSearch(search)
  96. }
  97. const post = async () => {
  98. try {
  99. const gif = gifs.find(g => g.id === selected)
  100. if (!gif) return
  101. setPosting(true)
  102. await communicator.post({
  103. attachments: [{
  104. url: gif.images.fixed_height.url,
  105. text: gif.title,
  106. }],
  107. data: {
  108. search,
  109. },
  110. visible: true,
  111. })
  112. setSearch('')
  113. setSelected('')
  114. } catch (err) {
  115. console.error(err)
  116. }
  117. setPosting(false)
  118. }
  119. const getPlaceholder = () => {
  120. const searches = settings.searches as string[] ?? []
  121. return searches[0] ? `Last Search: ${searches[0]}` : 'Search'
  122. }
  123. return (
  124. <div style={{ backgroundColor: theme.backgroundPrimary }}>
  125. <div className="field">
  126. <div className="control-container">
  127. <div className="icon" style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }}>
  128. <FontAwesomeIcon icon={faSearch} />
  129. </div>
  130. <div className="control">
  131. <input
  132. style={{ backgroundColor: theme.backgroundSecondary, borderColor: theme.secondary, color: theme.text }}
  133. type="text"
  134. placeholder={getPlaceholder()}
  135. value={search}
  136. onChange={(e) => handleSearchChange(e.target.value)} />
  137. </div>
  138. </div>
  139. </div>
  140. <div className="gifs">
  141. {gifs.map(gif => <Gif key={gif.id} gif={gif} selected={gif.id === selected} onSelect={setSelected} />)}
  142. </div>
  143. <nav className="level">
  144. <div>
  145. <img src={giphyIcon} style={{ width: 50, height: 18 }} />
  146. </div>
  147. <div>
  148. <button style={{ backgroundColor: theme.primary, color: theme.primaryAlternate }} onClick={() => post()} disabled={posting || !selected}>
  149. {posting ? <FontAwesomeIcon icon={faSpinner} spin /> : <span>Post</span>}
  150. </button>
  151. </div>
  152. </nav>
  153. </div>
  154. )
  155. }
  156. export default App