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

163 lines
4.5 KiB

5 years ago
  1. import React, { FC, useState, useEffect } from 'react'
  2. import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
  3. import { faSearch } from '@fortawesome/free-solid-svg-icons'
  4. import { Communicator } from '../../../communicator'
  5. import classNames from 'classnames'
  6. import Gif from './gif'
  7. import { ClassDictionary, GiphyGif } from '../../../types'
  8. const giphyIcon = require('./giphy.png')
  9. import '../../../styles/default.scss'
  10. type APIResponse = GiphyGif[]
  11. interface Props {
  12. communicator: Communicator
  13. }
  14. const useDebounce = (value: string, delay = 500) => {
  15. const [valueD, setValueD] = useState(value)
  16. useEffect(() => {
  17. const handler = setTimeout(() => {
  18. setValueD(value)
  19. }, delay)
  20. return () => {
  21. clearTimeout(handler)
  22. }
  23. }, [value, delay])
  24. return valueD
  25. }
  26. const App: FC<Props> = ({ communicator }) => {
  27. const [posting, setPosting] = useState(false)
  28. const [searching, setSearching] = useState(false)
  29. const [search, setSearch] = useState('')
  30. const searchD = useDebounce(search)
  31. const [gifs, setGifs] = useState<GiphyGif[]>([])
  32. const [selected, setSelected] = useState('')
  33. const buttonStyle: ClassDictionary = {
  34. 'button': true,
  35. 'is-primary': true,
  36. 'is-loading': posting,
  37. }
  38. const controlStyle: ClassDictionary = {
  39. 'control': true,
  40. 'has-icons-left': true,
  41. 'is-loading': searching,
  42. }
  43. useEffect(() => {
  44. const init = async () => {
  45. try {
  46. const content = await communicator.init()
  47. if (content && content.parent && content.parent.data && content.parent.data.search) {
  48. setSearch(content.parent.data.search)
  49. }
  50. await communicator.setHeight(1000)
  51. } catch (err) {
  52. console.error(err)
  53. }
  54. }
  55. init()
  56. }, [])
  57. useEffect(() => {
  58. const doTrending = async () => {
  59. try {
  60. const response = await fetch('/api/gifs/home')
  61. setGifs(await response.json() as APIResponse)
  62. } catch (err) {
  63. console.error(err)
  64. }
  65. }
  66. const doSearch = async () => {
  67. try {
  68. setSearching(true)
  69. const response = await fetch(`/api/gifs/search?q=${search}`)
  70. setGifs(await response.json() as APIResponse)
  71. } catch (err) {
  72. console.error(err)
  73. }
  74. setSearching(false)
  75. }
  76. if (!search) {
  77. doTrending()
  78. } else if (search.length > 2) {
  79. doSearch()
  80. }
  81. }, [searchD])
  82. const handleSearchChange = (search: string) => {
  83. setSearch(search)
  84. }
  85. const post = async () => {
  86. try {
  87. const gif = gifs.find(g => g.id === selected)
  88. if (!gif) return
  89. setPosting(true)
  90. await communicator.post({
  91. attachments: [{
  92. url: gif.images.fixed_height.url,
  93. text: gif.title,
  94. }],
  95. data: {
  96. search,
  97. },
  98. visible: true,
  99. })
  100. setSearch('')
  101. setSelected('')
  102. } catch (err) {
  103. console.error(err)
  104. }
  105. setPosting(false)
  106. }
  107. return (
  108. <div>
  109. <div className="field">
  110. <p className={classNames(controlStyle)}>
  111. <input className="input" type="text" placeholder="Search" value={search} onChange={(e) => handleSearchChange(e.target.value)} />
  112. <span className="icon is-small is-left">
  113. <FontAwesomeIcon icon={faSearch} />
  114. </span>
  115. </p>
  116. </div>
  117. <div className="gifs is-flex">
  118. {gifs.map(gif => <Gif key={gif.id} gif={gif} selected={gif.id === selected} onSelect={setSelected} />)}
  119. </div>
  120. <nav className="level">
  121. <div className="level-left">
  122. <div className="level-item">
  123. <img src={giphyIcon} />
  124. </div>
  125. </div>
  126. <div className="level-right">
  127. <div className="level-item">
  128. <button className={classNames(buttonStyle)} onClick={() => post()} disabled={!selected}>Post</button>
  129. </div>
  130. </div>
  131. </nav>
  132. </div>
  133. )
  134. }
  135. export default App