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.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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, setVirtualBackground } from '../actions';
  13. import { resizeImage, toDataURL } from '../functions';
  14. import logger from '../logger';
  15. // The limit of virtual background uploads is 21. When the number
  16. // of uploads is 22 we trigger the deleteStoredImage function to delete
  17. // the first/oldest uploaded background.
  18. const backgroundsLimit = 22;
  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. jitsiLocalStorage.setItem('virtualBackgrounds', JSON.stringify(storedImages));
  64. if (storedImages.length === backgroundsLimit) {
  65. deleteStoredImage(storedImages[0]);
  66. }
  67. }, [ storedImages ]);
  68. const [ selected, setSelected ] = useState('');
  69. const enableBlur = async () => {
  70. isloading(true);
  71. setSelected('blur');
  72. await dispatch(setVirtualBackground('', false));
  73. await dispatch(toggleBackgroundEffect(true));
  74. isloading(false);
  75. };
  76. const removeBackground = async () => {
  77. isloading(true);
  78. setSelected('none');
  79. await dispatch(setVirtualBackground('', false));
  80. await dispatch(toggleBackgroundEffect(false));
  81. isloading(false);
  82. };
  83. const setUploadedImageBackground = async image => {
  84. isloading(true);
  85. setSelected(image.id);
  86. await dispatch(setVirtualBackground(image.src, true));
  87. await dispatch(toggleBackgroundEffect(true));
  88. isloading(false);
  89. };
  90. const setImageBackground = async image => {
  91. isloading(true);
  92. setSelected(image.id);
  93. await dispatch(setVirtualBackground(await toDataURL(image.src), true));
  94. await dispatch(toggleBackgroundEffect(true));
  95. isloading(false);
  96. };
  97. const uploadImage = async imageFile => {
  98. const reader = new FileReader();
  99. reader.readAsDataURL(imageFile[0]);
  100. reader.onload = async () => {
  101. const resizedImage = await resizeImage(reader.result);
  102. isloading(true);
  103. setStoredImages([
  104. ...storedImages,
  105. {
  106. id: uuid.v4(),
  107. src: resizedImage
  108. }
  109. ]);
  110. await dispatch(setVirtualBackground(resizedImage, true));
  111. await dispatch(toggleBackgroundEffect(true));
  112. isloading(false);
  113. };
  114. reader.onerror = () => {
  115. isloading(false);
  116. logger.error('Failed to upload virtual image!');
  117. };
  118. };
  119. return (
  120. <Dialog
  121. hideCancelButton = { true }
  122. submitDisabled = { false }
  123. titleKey = { 'virtualBackground.title' }
  124. width = 'small'>
  125. {loading ? (
  126. <div className = 'virtual-background-loading'>
  127. <span className = 'loading-content-text'>{t('virtualBackground.pleaseWait')}</span>
  128. <Spinner
  129. isCompleting = { false }
  130. size = 'medium' />
  131. </div>
  132. ) : (
  133. <div>
  134. <div className = 'virtual-background-dialog'>
  135. <Tooltip
  136. content = { t('virtualBackground.removeBackground') }
  137. position = { 'top' }>
  138. <div
  139. className = { selected === 'none' ? 'none-selected' : 'virtual-background-none' }
  140. onClick = { removeBackground }>
  141. {t('virtualBackground.none')}
  142. </div>
  143. </Tooltip>
  144. <Tooltip
  145. content = { t('virtualBackground.enableBlur') }
  146. position = { 'top' }>
  147. <Icon
  148. className = { selected === 'blur' ? 'blur-selected' : '' }
  149. onClick = { () => enableBlur() }
  150. size = { 50 }
  151. src = { IconBlurBackground } />
  152. </Tooltip>
  153. {images.map((image, index) => (
  154. <img
  155. className = { selected === image.id ? 'thumbnail-selected' : 'thumbnail' }
  156. key = { index }
  157. onClick = { () => setImageBackground(image) }
  158. onError = { event => event.target.style.display = 'none' }
  159. src = { image.src } />
  160. ))}
  161. <Tooltip
  162. content = { t('virtualBackground.uploadImage') }
  163. position = { 'top' }>
  164. <label
  165. className = 'custom-file-upload'
  166. htmlFor = 'file-upload'>
  167. +
  168. </label>
  169. <input
  170. accept = 'image/*'
  171. className = 'file-upload-btn'
  172. id = 'file-upload'
  173. onChange = { e => uploadImage(e.target.files) }
  174. type = 'file' />
  175. </Tooltip>
  176. </div>
  177. <div className = 'virtual-background-dialog'>
  178. {storedImages.map((image, index) => (
  179. <div
  180. className = { 'thumbnail-container' }
  181. key = { index }>
  182. <img
  183. className = { selected === image.id ? 'thumbnail-selected' : 'thumbnail' }
  184. onClick = { () => setUploadedImageBackground(image) }
  185. onError = { event => event.target.style.display = 'none' }
  186. src = { image.src } />
  187. <Icon
  188. className = { 'delete-image-icon' }
  189. onClick = { () => deleteStoredImage(image) }
  190. size = { 15 }
  191. src = { IconCancelSelection } />
  192. </div>
  193. ))}
  194. </div>
  195. </div>
  196. )}
  197. </Dialog>
  198. );
  199. }
  200. export default translate(connect()(VirtualBackground));