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.

VirtualBackgroundDialog.js 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. // @flow
  2. /* eslint-disable react/jsx-no-bind, no-return-assign */
  3. import Spinner from '@atlaskit/spinner';
  4. import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
  5. import React, { useState, useEffect } from 'react';
  6. import uuid from 'uuid';
  7. import { Dialog } from '../../base/dialog';
  8. import { translate } from '../../base/i18n';
  9. import { Icon, IconBlurBackground, IconCancelSelection } from '../../base/icons';
  10. import { connect } from '../../base/redux';
  11. import { Tooltip } from '../../base/tooltip';
  12. import { toggleBackgroundEffect } from '../actions';
  13. import { resizeImage, toDataURL } from '../functions';
  14. import logger from '../logger';
  15. // The limit of virtual background uploads is 24. When the number
  16. // of uploads is 25 we trigger the deleteStoredImage function to delete
  17. // the first/oldest uploaded background.
  18. const backgroundsLimit = 25;
  19. const images = [
  20. {
  21. id: 1,
  22. src: 'images/virtual-background/background-1.jpg'
  23. },
  24. {
  25. id: 2,
  26. src: 'images/virtual-background/background-2.jpg'
  27. },
  28. {
  29. id: 3,
  30. src: 'images/virtual-background/background-3.jpg'
  31. },
  32. {
  33. id: 4,
  34. src: 'images/virtual-background/background-4.jpg'
  35. }
  36. ];
  37. type Props = {
  38. /**
  39. * The redux {@code dispatch} function.
  40. */
  41. dispatch: Function,
  42. /**
  43. * Invoked to obtain translated strings.
  44. */
  45. t: Function
  46. };
  47. /**
  48. * Renders virtual background dialog.
  49. *
  50. * @returns {ReactElement}
  51. */
  52. function VirtualBackground({ dispatch, t }: Props) {
  53. const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
  54. const [ storedImages, setStoredImages ] = useState((localImages && JSON.parse(localImages)) || []);
  55. const [ loading, isloading ] = useState(false);
  56. const deleteStoredImage = image => {
  57. setStoredImages(storedImages.filter(item => item !== image));
  58. };
  59. /**
  60. * Updates stored images on local storage.
  61. */
  62. useEffect(() => {
  63. try {
  64. jitsiLocalStorage.setItem('virtualBackgrounds', JSON.stringify(storedImages));
  65. } catch (err) {
  66. // Preventing localStorage QUOTA_EXCEEDED_ERR
  67. err && deleteStoredImage(storedImages[0]);
  68. }
  69. if (storedImages.length === backgroundsLimit) {
  70. deleteStoredImage(storedImages[0]);
  71. }
  72. }, [ storedImages ]);
  73. const [ selected, setSelected ] = useState('');
  74. const enableBlur = async (blurValue, selection) => {
  75. isloading(true);
  76. setSelected(selection);
  77. await dispatch(
  78. toggleBackgroundEffect({
  79. backgroundType: 'blur',
  80. enabled: true,
  81. blurValue
  82. })
  83. );
  84. isloading(false);
  85. };
  86. const removeBackground = async () => {
  87. isloading(true);
  88. setSelected('none');
  89. await dispatch(
  90. toggleBackgroundEffect({
  91. enabled: false
  92. })
  93. );
  94. isloading(false);
  95. };
  96. const setUploadedImageBackground = async image => {
  97. isloading(true);
  98. setSelected(image.id);
  99. await dispatch(
  100. toggleBackgroundEffect({
  101. backgroundType: 'image',
  102. enabled: true,
  103. url: image.src
  104. })
  105. );
  106. isloading(false);
  107. };
  108. const setImageBackground = async image => {
  109. isloading(true);
  110. setSelected(image.id);
  111. const url = await toDataURL(image.src);
  112. await dispatch(
  113. toggleBackgroundEffect({
  114. backgroundType: 'image',
  115. enabled: true,
  116. url
  117. })
  118. );
  119. isloading(false);
  120. };
  121. const uploadImage = async imageFile => {
  122. const reader = new FileReader();
  123. reader.readAsDataURL(imageFile[0]);
  124. reader.onload = async () => {
  125. const url = await resizeImage(reader.result);
  126. isloading(true);
  127. setStoredImages([
  128. ...storedImages,
  129. {
  130. id: uuid.v4(),
  131. src: url
  132. }
  133. ]);
  134. await dispatch(
  135. toggleBackgroundEffect({
  136. backgroundType: 'image',
  137. enabled: true,
  138. url
  139. })
  140. );
  141. isloading(false);
  142. };
  143. reader.onerror = () => {
  144. isloading(false);
  145. logger.error('Failed to upload virtual image!');
  146. };
  147. };
  148. return (
  149. <Dialog
  150. hideCancelButton = { true }
  151. submitDisabled = { false }
  152. titleKey = { 'virtualBackground.title' }
  153. width = '450px'>
  154. {loading ? (
  155. <div className = 'virtual-background-loading'>
  156. <span className = 'loading-content-text'>{t('virtualBackground.pleaseWait')}</span>
  157. <Spinner
  158. isCompleting = { false }
  159. size = 'medium' />
  160. </div>
  161. ) : (
  162. <div>
  163. <div className = 'virtual-background-dialog'>
  164. <Tooltip
  165. content = { t('virtualBackground.removeBackground') }
  166. position = { 'top' }>
  167. <div
  168. className = { selected === 'none' ? 'none-selected' : 'virtual-background-none' }
  169. onClick = { removeBackground }>
  170. {t('virtualBackground.none')}
  171. </div>
  172. </Tooltip>
  173. <Tooltip
  174. content = { t('virtualBackground.slightBlur') }
  175. position = { 'top' }>
  176. <Icon
  177. className = { selected === 'slight-blur' ? 'blur-selected' : '' }
  178. onClick = { () => enableBlur(8, 'slight-blur') }
  179. size = { 50 }
  180. src = { IconBlurBackground } />
  181. </Tooltip>
  182. <Tooltip
  183. content = { t('virtualBackground.blur') }
  184. position = { 'top' }>
  185. <Icon
  186. className = { selected === 'blur' ? 'blur-selected' : '' }
  187. onClick = { () => enableBlur(25, 'blur') }
  188. size = { 50 }
  189. src = { IconBlurBackground } />
  190. </Tooltip>
  191. {images.map((image, index) => (
  192. <img
  193. className = { selected === image.id ? 'thumbnail-selected' : 'thumbnail' }
  194. key = { index }
  195. onClick = { () => setImageBackground(image) }
  196. onError = { event => event.target.style.display = 'none' }
  197. src = { image.src } />
  198. ))}
  199. <Tooltip
  200. content = { t('virtualBackground.uploadImage') }
  201. position = { 'top' }>
  202. <label
  203. className = 'custom-file-upload'
  204. htmlFor = 'file-upload'>
  205. +
  206. </label>
  207. <input
  208. accept = 'image/*'
  209. className = 'file-upload-btn'
  210. id = 'file-upload'
  211. onChange = { e => uploadImage(e.target.files) }
  212. type = 'file' />
  213. </Tooltip>
  214. </div>
  215. <div className = 'virtual-background-dialog'>
  216. {storedImages.map((image, index) => (
  217. <div
  218. className = { 'thumbnail-container' }
  219. key = { index }>
  220. <img
  221. className = { selected === image.id ? 'thumbnail-selected' : 'thumbnail' }
  222. onClick = { () => setUploadedImageBackground(image) }
  223. onError = { event => event.target.style.display = 'none' }
  224. src = { image.src } />
  225. <Icon
  226. className = { 'delete-image-icon' }
  227. onClick = { () => deleteStoredImage(image) }
  228. size = { 15 }
  229. src = { IconCancelSelection } />
  230. </div>
  231. ))}
  232. </div>
  233. </div>
  234. )}
  235. </Dialog>
  236. );
  237. }
  238. export default translate(connect()(VirtualBackground));