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 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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. * Returns the selected thumbnail identifier.
  40. */
  41. _selectedThumbnail: string,
  42. /**
  43. * The redux {@code dispatch} function.
  44. */
  45. dispatch: Function,
  46. /**
  47. * Invoked to obtain translated strings.
  48. */
  49. t: Function
  50. };
  51. /**
  52. * Renders virtual background dialog.
  53. *
  54. * @returns {ReactElement}
  55. */
  56. function VirtualBackground({ _selectedThumbnail, dispatch, t }: Props) {
  57. const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
  58. const [ storedImages, setStoredImages ] = useState((localImages && JSON.parse(localImages)) || []);
  59. const [ loading, isloading ] = useState(false);
  60. const deleteStoredImage = image => {
  61. setStoredImages(storedImages.filter(item => item !== image));
  62. };
  63. /**
  64. * Updates stored images on local storage.
  65. */
  66. useEffect(() => {
  67. try {
  68. jitsiLocalStorage.setItem('virtualBackgrounds', JSON.stringify(storedImages));
  69. } catch (err) {
  70. // Preventing localStorage QUOTA_EXCEEDED_ERR
  71. err && deleteStoredImage(storedImages[0]);
  72. }
  73. if (storedImages.length === backgroundsLimit) {
  74. deleteStoredImage(storedImages[0]);
  75. }
  76. }, [ storedImages ]);
  77. const enableBlur = async (blurValue, selection) => {
  78. isloading(true);
  79. await dispatch(
  80. toggleBackgroundEffect({
  81. backgroundType: 'blur',
  82. enabled: true,
  83. blurValue,
  84. selectedThumbnail: selection
  85. })
  86. );
  87. isloading(false);
  88. };
  89. const removeBackground = async () => {
  90. isloading(true);
  91. await dispatch(
  92. toggleBackgroundEffect({
  93. enabled: false,
  94. selectedThumbnail: 'none'
  95. })
  96. );
  97. isloading(false);
  98. };
  99. const setUploadedImageBackground = async image => {
  100. isloading(true);
  101. await dispatch(
  102. toggleBackgroundEffect({
  103. backgroundType: 'image',
  104. enabled: true,
  105. url: image.src,
  106. selectedThumbnail: image.id
  107. })
  108. );
  109. isloading(false);
  110. };
  111. const setImageBackground = async image => {
  112. isloading(true);
  113. const url = await toDataURL(image.src);
  114. await dispatch(
  115. toggleBackgroundEffect({
  116. backgroundType: 'image',
  117. enabled: true,
  118. url,
  119. selectedThumbnail: image.id
  120. })
  121. );
  122. isloading(false);
  123. };
  124. const uploadImage = async imageFile => {
  125. const reader = new FileReader();
  126. reader.readAsDataURL(imageFile[0]);
  127. reader.onload = async () => {
  128. const url = await resizeImage(reader.result);
  129. const uuId = uuid.v4();
  130. isloading(true);
  131. setStoredImages([
  132. ...storedImages,
  133. {
  134. id: uuId,
  135. src: url
  136. }
  137. ]);
  138. await dispatch(
  139. toggleBackgroundEffect({
  140. backgroundType: 'image',
  141. enabled: true,
  142. url,
  143. selectedThumbnail: uuId
  144. })
  145. );
  146. isloading(false);
  147. };
  148. reader.onerror = () => {
  149. isloading(false);
  150. logger.error('Failed to upload virtual image!');
  151. };
  152. };
  153. return (
  154. <Dialog
  155. hideCancelButton = { true }
  156. submitDisabled = { false }
  157. titleKey = { 'virtualBackground.title' }
  158. width = '450px'>
  159. {loading ? (
  160. <div className = 'virtual-background-loading'>
  161. <span className = 'loading-content-text'>{t('virtualBackground.pleaseWait')}</span>
  162. <Spinner
  163. isCompleting = { false }
  164. size = 'medium' />
  165. </div>
  166. ) : (
  167. <div>
  168. <div className = 'virtual-background-dialog'>
  169. <Tooltip
  170. content = { t('virtualBackground.removeBackground') }
  171. position = { 'top' }>
  172. <div
  173. className = { _selectedThumbnail === 'none'
  174. ? 'none-selected' : 'virtual-background-none' }
  175. onClick = { removeBackground }>
  176. {t('virtualBackground.none')}
  177. </div>
  178. </Tooltip>
  179. <Tooltip
  180. content = { t('virtualBackground.slightBlur') }
  181. position = { 'top' }>
  182. <Icon
  183. className = { _selectedThumbnail === 'slight-blur' ? 'blur-selected' : '' }
  184. onClick = { () => enableBlur(8, 'slight-blur') }
  185. size = { 50 }
  186. src = { IconBlurBackground } />
  187. </Tooltip>
  188. <Tooltip
  189. content = { t('virtualBackground.blur') }
  190. position = { 'top' }>
  191. <Icon
  192. className = { _selectedThumbnail === 'blur' ? 'blur-selected' : '' }
  193. onClick = { () => enableBlur(25, 'blur') }
  194. size = { 50 }
  195. src = { IconBlurBackground } />
  196. </Tooltip>
  197. {images.map((image, index) => (
  198. <img
  199. className = { _selectedThumbnail === image.id ? 'thumbnail-selected' : 'thumbnail' }
  200. key = { index }
  201. onClick = { () => setImageBackground(image) }
  202. onError = { event => event.target.style.display = 'none' }
  203. src = { image.src } />
  204. ))}
  205. <Tooltip
  206. content = { t('virtualBackground.uploadImage') }
  207. position = { 'top' }>
  208. <label
  209. className = 'custom-file-upload'
  210. htmlFor = 'file-upload'>
  211. +
  212. </label>
  213. <input
  214. accept = 'image/*'
  215. className = 'file-upload-btn'
  216. id = 'file-upload'
  217. onChange = { e => uploadImage(e.target.files) }
  218. type = 'file' />
  219. </Tooltip>
  220. </div>
  221. <div className = 'virtual-background-dialog'>
  222. {storedImages.map((image, index) => (
  223. <div
  224. className = { 'thumbnail-container' }
  225. key = { index }>
  226. <img
  227. className = { _selectedThumbnail === image.id ? 'thumbnail-selected' : 'thumbnail' }
  228. onClick = { () => setUploadedImageBackground(image) }
  229. onError = { event => event.target.style.display = 'none' }
  230. src = { image.src } />
  231. <Icon
  232. className = { 'delete-image-icon' }
  233. onClick = { () => deleteStoredImage(image) }
  234. size = { 15 }
  235. src = { IconCancelSelection } />
  236. </div>
  237. ))}
  238. </div>
  239. </div>
  240. )}
  241. </Dialog>
  242. );
  243. }
  244. /**
  245. * Maps (parts of) the redux state to the associated props for the
  246. * {@code VirtualBackground} component.
  247. *
  248. * @param {Object} state - The Redux state.
  249. * @private
  250. * @returns {{
  251. * _selectedThumbnail: string
  252. * }}
  253. */
  254. function _mapStateToProps(state): Object {
  255. return {
  256. _selectedThumbnail: state['features/virtual-background'].selectedThumbnail
  257. };
  258. }
  259. export default translate(connect(_mapStateToProps)(VirtualBackground));