feat(dynamic-branding): Add branding option for virtual backgrounds
This commit is contained in:
parent
57083c174f
commit
f9cc813e91
|
@ -909,6 +909,10 @@ var config = {
|
|||
*/
|
||||
// dynamicBrandingUrl: '',
|
||||
|
||||
// When true the user cannot add more images to be used as virtual background.
|
||||
// Only the default ones from will be available.
|
||||
// disableAddingBackgroundImages: false,
|
||||
|
||||
// Sets the background transparency level. '0' is fully transparent, '1' is opaque.
|
||||
// backgroundAlpha: 1,
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import '../authentication/middleware';
|
||||
import '../base/devices/middleware';
|
||||
import '../dynamic-branding/middleware';
|
||||
import '../e2ee/middleware';
|
||||
import '../external-api/middleware';
|
||||
import '../keyboard-shortcuts/middleware';
|
||||
|
|
|
@ -84,6 +84,7 @@ export default [
|
|||
'disableAEC',
|
||||
'disableAGC',
|
||||
'disableAP',
|
||||
'disableAddingBackgroundImages',
|
||||
'disableAudioLevels',
|
||||
'disableChatSmileys',
|
||||
'disableDeepLinking',
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
* @param {string} path - The URL path.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function extractFqnFromPath(path: string) {
|
||||
const parts = path.split('/');
|
||||
export function extractFqnFromPath() {
|
||||
const parts = window.location.pathname.split('/');
|
||||
const len = parts.length;
|
||||
|
||||
return parts.length > 2 ? `${parts[len - 2]}/${parts[len - 1]}` : '';
|
||||
|
@ -28,7 +28,7 @@ export function getDynamicBrandingUrl(state: Object) {
|
|||
}
|
||||
|
||||
const baseUrl = state['features/base/config'].brandingDataUrl;
|
||||
const fqn = extractFqnFromPath(state['features/base/connection'].locationURL.pathname);
|
||||
const fqn = extractFqnFromPath();
|
||||
|
||||
if (baseUrl && fqn) {
|
||||
return `${baseUrl}?conferenceFqn=${encodeURIComponent(fqn)}`;
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
// @flow
|
||||
|
||||
import { APP_WILL_MOUNT } from '../base/app';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { fetchCustomBrandingData } from './actions';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT: {
|
||||
|
||||
store.dispatch(fetchCustomBrandingData());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
import { type Image } from '../virtual-background/constants';
|
||||
|
||||
import {
|
||||
SET_DYNAMIC_BRANDING_DATA,
|
||||
|
@ -113,7 +114,15 @@ const DEFAULT_STATE = {
|
|||
* @public
|
||||
* @type {boolean}
|
||||
*/
|
||||
useDynamicBrandingData: false
|
||||
useDynamicBrandingData: false,
|
||||
|
||||
/**
|
||||
* An array of images to be used as virtual backgrounds instead of the default ones.
|
||||
*
|
||||
* @public
|
||||
* @type {Array<Object>}
|
||||
*/
|
||||
virtualBackgrounds: []
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -131,7 +140,8 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
|||
inviteDomain,
|
||||
logoClickUrl,
|
||||
logoImageUrl,
|
||||
premeetingBackground
|
||||
premeetingBackground,
|
||||
virtualBackgrounds
|
||||
} = action.value;
|
||||
|
||||
return {
|
||||
|
@ -146,7 +156,8 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
|||
premeetingBackground,
|
||||
customizationFailed: false,
|
||||
customizationReady: true,
|
||||
useDynamicBrandingData: true
|
||||
useDynamicBrandingData: true,
|
||||
virtualBackgrounds: formatImages(virtualBackgrounds || [])
|
||||
};
|
||||
}
|
||||
case SET_DYNAMIC_BRANDING_FAILED: {
|
||||
|
@ -166,3 +177,30 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
|||
|
||||
return state;
|
||||
});
|
||||
|
||||
/**
|
||||
* Transforms the branding images into an array of Images objects ready
|
||||
* to be used as virtual backgrounds.
|
||||
*
|
||||
* @param {Array<string>} images -
|
||||
* @private
|
||||
* @returns {{Props}}
|
||||
*/
|
||||
function formatImages(images: Array<string> | Array<Object>): Array<Image> {
|
||||
return images.map((img, i) => {
|
||||
let src;
|
||||
let tooltip;
|
||||
|
||||
if (typeof img === 'object') {
|
||||
({ src, tooltip } = img);
|
||||
} else {
|
||||
src = img;
|
||||
}
|
||||
|
||||
return {
|
||||
id: `branding-${i}`,
|
||||
src,
|
||||
tooltip
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -131,7 +131,7 @@ export function sendJaasFeedbackMetadata(conference: Object, feedback: Object) {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const meetingFqn = extractFqnFromPath(state['features/base/connection'].locationURL.pathname);
|
||||
const meetingFqn = extractFqnFromPath();
|
||||
const feedbackData = {
|
||||
...feedback,
|
||||
sessionId: conference.sessionId,
|
||||
|
|
|
@ -5,7 +5,6 @@ import React, { Component } from 'react';
|
|||
import { Watermarks } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
import { setColorAlpha } from '../../base/util';
|
||||
import { fetchCustomBrandingData } from '../../dynamic-branding';
|
||||
import { SharedVideo } from '../../shared-video/components/web';
|
||||
import { Captions } from '../../subtitles/';
|
||||
|
||||
|
@ -28,11 +27,6 @@ type Props = {
|
|||
*/
|
||||
_customBackgroundImageUrl: string,
|
||||
|
||||
/**
|
||||
* Fetches the branding data.
|
||||
*/
|
||||
_fetchCustomBrandingData: Function,
|
||||
|
||||
/**
|
||||
* Prop that indicates whether the chat is open.
|
||||
*/
|
||||
|
@ -52,14 +46,6 @@ type Props = {
|
|||
* @extends Component
|
||||
*/
|
||||
class LargeVideo extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
this.props._fetchCustomBrandingData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
|
@ -167,8 +153,4 @@ function _mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
const _mapDispatchToProps = {
|
||||
_fetchCustomBrandingData: fetchCustomBrandingData
|
||||
};
|
||||
|
||||
export default connect(_mapStateToProps, _mapDispatchToProps)(LargeVideo);
|
||||
export default connect(_mapStateToProps)(LargeVideo);
|
||||
|
|
|
@ -55,7 +55,6 @@ export async function sendReactionsWebhook(state: Object, reactions: Array<?stri
|
|||
const { webhookProxyUrl: url } = state['features/base/config'];
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
const headers = {
|
||||
|
@ -65,7 +64,7 @@ export async function sendReactionsWebhook(state: Object, reactions: Array<?stri
|
|||
|
||||
|
||||
const reqBody = {
|
||||
meetingFqn: extractFqnFromPath(locationURL.pathname),
|
||||
meetingFqn: extractFqnFromPath(),
|
||||
sessionId: conference.sessionId,
|
||||
submitted: Date.now(),
|
||||
reactions,
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
// @flow
|
||||
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import uuid from 'uuid';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconPlusCircle } from '../../base/icons';
|
||||
import { VIRTUAL_BACKGROUND_TYPE, type Image } from '../constants';
|
||||
import { resizeImage } from '../functions';
|
||||
import logger from '../logger';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Callback used to set the 'loading' state of the parent component.
|
||||
*/
|
||||
setLoading: Function,
|
||||
|
||||
/**
|
||||
* Callback used to set the options.
|
||||
*/
|
||||
setOptions: Function,
|
||||
|
||||
/**
|
||||
* Callback used to set the storedImages array.
|
||||
*/
|
||||
setStoredImages: Function,
|
||||
|
||||
/**
|
||||
* A list of images locally stored.
|
||||
*/
|
||||
storedImages: Array<Image>,
|
||||
|
||||
/**
|
||||
* If a label should be displayed alongside the button.
|
||||
*/
|
||||
showLabel: boolean,
|
||||
|
||||
/**
|
||||
* Used for translation.
|
||||
*/
|
||||
t: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* Component used to upload an image.
|
||||
*
|
||||
* @param {Object} Props - The props of the component.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
function UploadImageButton({
|
||||
setLoading,
|
||||
setOptions,
|
||||
setStoredImages,
|
||||
showLabel,
|
||||
storedImages,
|
||||
t
|
||||
}: Props) {
|
||||
const uploadImageButton: Object = useRef(null);
|
||||
const uploadImageKeyPress = useCallback(e => {
|
||||
if (uploadImageButton.current && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
uploadImageButton.current.click();
|
||||
}
|
||||
}, [ uploadImageButton.current ]);
|
||||
|
||||
|
||||
const uploadImage = useCallback(async e => {
|
||||
const reader = new FileReader();
|
||||
const imageFile = e.target.files;
|
||||
|
||||
reader.readAsDataURL(imageFile[0]);
|
||||
reader.onload = async () => {
|
||||
const url = await resizeImage(reader.result);
|
||||
const uuId = uuid.v4();
|
||||
|
||||
setStoredImages([
|
||||
...storedImages,
|
||||
{
|
||||
id: uuId,
|
||||
src: url
|
||||
}
|
||||
]);
|
||||
setOptions({
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
|
||||
enabled: true,
|
||||
url,
|
||||
selectedThumbnail: uuId
|
||||
});
|
||||
};
|
||||
logger.info('New virtual background image uploaded!');
|
||||
|
||||
reader.onerror = () => {
|
||||
setLoading(false);
|
||||
logger.error('Failed to upload virtual image!');
|
||||
};
|
||||
}, [ storedImages ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showLabel && <label
|
||||
aria-label = { t('virtualBackground.uploadImage') }
|
||||
className = 'file-upload-label'
|
||||
htmlFor = 'file-upload'
|
||||
onKeyPress = { uploadImageKeyPress }
|
||||
tabIndex = { 0 } >
|
||||
<Icon
|
||||
className = { 'add-background' }
|
||||
size = { 20 }
|
||||
src = { IconPlusCircle } />
|
||||
{t('virtualBackground.addBackground')}
|
||||
</label>}
|
||||
|
||||
<input
|
||||
accept = 'image/*'
|
||||
className = 'file-upload-btn'
|
||||
id = 'file-upload'
|
||||
onChange = { uploadImage }
|
||||
ref = { uploadImageButton }
|
||||
type = 'file' />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(UploadImageButton);
|
|
@ -3,12 +3,11 @@
|
|||
import Spinner from '@atlaskit/spinner';
|
||||
import Bourne from '@hapi/bourne';
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import uuid from 'uuid';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
import { Dialog, hideDialog, openDialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconCloseSmall, IconPlusCircle, IconShareDesktop } from '../../base/icons';
|
||||
import { Icon, IconCloseSmall, IconShareDesktop } from '../../base/icons';
|
||||
import { browser, JitsiTrackErrors } from '../../base/lib-jitsi-meet';
|
||||
import { createLocalTrack } from '../../base/lib-jitsi-meet/functions';
|
||||
import { VIDEO_TYPE } from '../../base/media';
|
||||
|
@ -18,62 +17,20 @@ import { Tooltip } from '../../base/tooltip';
|
|||
import { getLocalVideoTrack } from '../../base/tracks';
|
||||
import { showErrorNotification } from '../../notifications';
|
||||
import { toggleBackgroundEffect } from '../actions';
|
||||
import { VIRTUAL_BACKGROUND_TYPE } from '../constants';
|
||||
import { resizeImage, toDataURL } from '../functions';
|
||||
import { IMAGES, BACKGROUNDS_LIMIT, VIRTUAL_BACKGROUND_TYPE, type Image } from '../constants';
|
||||
import { toDataURL } from '../functions';
|
||||
import logger from '../logger';
|
||||
|
||||
import UploadImageButton from './UploadImageButton';
|
||||
import VirtualBackgroundPreview from './VirtualBackgroundPreview';
|
||||
|
||||
|
||||
type Image = {
|
||||
tooltip?: string,
|
||||
id: string,
|
||||
src: string
|
||||
}
|
||||
|
||||
// The limit of virtual background uploads is 24. When the number
|
||||
// of uploads is 25 we trigger the deleteStoredImage function to delete
|
||||
// the first/oldest uploaded background.
|
||||
const backgroundsLimit = 25;
|
||||
const images: Array<Image> = [
|
||||
{
|
||||
tooltip: 'image1',
|
||||
id: '1',
|
||||
src: 'images/virtual-background/background-1.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image2',
|
||||
id: '2',
|
||||
src: 'images/virtual-background/background-2.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image3',
|
||||
id: '3',
|
||||
src: 'images/virtual-background/background-3.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image4',
|
||||
id: '4',
|
||||
src: 'images/virtual-background/background-4.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image5',
|
||||
id: '5',
|
||||
src: 'images/virtual-background/background-5.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image6',
|
||||
id: '6',
|
||||
src: 'images/virtual-background/background-6.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image7',
|
||||
id: '7',
|
||||
src: 'images/virtual-background/background-7.jpg'
|
||||
}
|
||||
];
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The list of Images to choose from.
|
||||
*/
|
||||
_images: Array<Image>,
|
||||
|
||||
/**
|
||||
* The current local flip x status.
|
||||
*/
|
||||
|
@ -89,6 +46,11 @@ type Props = {
|
|||
*/
|
||||
_selectedThumbnail: string,
|
||||
|
||||
/**
|
||||
* If the upload button should be displayed or not.
|
||||
*/
|
||||
_showUploadButton: boolean,
|
||||
|
||||
/**
|
||||
* Returns the selected virtual background object.
|
||||
*/
|
||||
|
@ -128,11 +90,15 @@ const onError = event => {
|
|||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
const { localFlipX } = state['features/base/settings'];
|
||||
const dynamicBrandingImages = state['features/dynamic-branding'].virtualBackgrounds;
|
||||
const hasBrandingImages = Boolean(dynamicBrandingImages.length);
|
||||
|
||||
return {
|
||||
_localFlipX: Boolean(localFlipX),
|
||||
_images: (hasBrandingImages && dynamicBrandingImages) || IMAGES,
|
||||
_virtualBackground: state['features/virtual-background'],
|
||||
_selectedThumbnail: state['features/virtual-background'].selectedThumbnail,
|
||||
_showUploadButton: !(hasBrandingImages || state['features/base/config'].disableAddingBackgroundImages),
|
||||
_jitsiTrack: getLocalVideoTrack(state['features/base/tracks'])?.jitsiTrack
|
||||
};
|
||||
}
|
||||
|
@ -145,9 +111,11 @@ const VirtualBackgroundDialog = translate(connect(_mapStateToProps)(VirtualBackg
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
function VirtualBackground({
|
||||
_localFlipX,
|
||||
_images,
|
||||
_jitsiTrack,
|
||||
_localFlipX,
|
||||
_selectedThumbnail,
|
||||
_showUploadButton,
|
||||
_virtualBackground,
|
||||
dispatch,
|
||||
initialOptions,
|
||||
|
@ -158,7 +126,7 @@ function VirtualBackground({
|
|||
const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
|
||||
const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && Bourne.parse(localImages)) || []);
|
||||
const [ loading, setLoading ] = useState(false);
|
||||
const uploadImageButton: Object = useRef(null);
|
||||
|
||||
const [ activeDesktopVideo ] = useState(_virtualBackground?.virtualSource?.videoType === VIDEO_TYPE.DESKTOP
|
||||
? _virtualBackground.virtualSource
|
||||
: null);
|
||||
|
@ -186,7 +154,7 @@ function VirtualBackground({
|
|||
// Preventing localStorage QUOTA_EXCEEDED_ERR
|
||||
err && setStoredImages(storedImages.slice(1));
|
||||
}
|
||||
if (storedImages.length === backgroundsLimit) {
|
||||
if (storedImages.length === BACKGROUNDS_LIMIT) {
|
||||
setStoredImages(storedImages.slice(1));
|
||||
}
|
||||
}, [ storedImages ]);
|
||||
|
@ -321,61 +289,27 @@ function VirtualBackground({
|
|||
|
||||
const setImageBackground = useCallback(async e => {
|
||||
const imageId = e.currentTarget.getAttribute('data-imageid');
|
||||
const image = images.find(img => img.id === imageId);
|
||||
const image = _images.find(img => img.id === imageId);
|
||||
|
||||
if (image) {
|
||||
const url = await toDataURL(image.src);
|
||||
try {
|
||||
const url = await toDataURL(image.src);
|
||||
|
||||
setOptions({
|
||||
backgroundType: 'image',
|
||||
enabled: true,
|
||||
url,
|
||||
selectedThumbnail: image.id
|
||||
});
|
||||
logger.info('Image setted for virtual background preview!');
|
||||
setOptions({
|
||||
backgroundType: 'image',
|
||||
enabled: true,
|
||||
url,
|
||||
selectedThumbnail: image.id
|
||||
});
|
||||
logger.info('Image set for virtual background preview!');
|
||||
} catch (err) {
|
||||
logger.error('Could not fetch virtual background image:', err);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const uploadImage = useCallback(async e => {
|
||||
const reader = new FileReader();
|
||||
const imageFile = e.target.files;
|
||||
|
||||
reader.readAsDataURL(imageFile[0]);
|
||||
reader.onload = async () => {
|
||||
const url = await resizeImage(reader.result);
|
||||
const uuId = uuid.v4();
|
||||
|
||||
setStoredImages([
|
||||
...storedImages,
|
||||
{
|
||||
id: uuId,
|
||||
src: url
|
||||
}
|
||||
]);
|
||||
setOptions({
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
|
||||
enabled: true,
|
||||
url,
|
||||
selectedThumbnail: uuId
|
||||
});
|
||||
};
|
||||
logger.info('New virtual background image uploaded!');
|
||||
|
||||
reader.onerror = () => {
|
||||
setLoading(false);
|
||||
logger.error('Failed to upload virtual image!');
|
||||
};
|
||||
}, [ dispatch, storedImages ]);
|
||||
|
||||
const uploadImageKeyPress = useCallback(e => {
|
||||
if (uploadImageButton.current && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
uploadImageButton.current.click();
|
||||
}
|
||||
}, [ uploadImageButton.current ]);
|
||||
|
||||
const setImageBackgroundKeyPress = useCallback(e => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
|
@ -448,25 +382,13 @@ function VirtualBackground({
|
|||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{previewIsLoaded && <label
|
||||
aria-label = { t('virtualBackground.uploadImage') }
|
||||
className = 'file-upload-label'
|
||||
htmlFor = 'file-upload'
|
||||
onKeyPress = { uploadImageKeyPress }
|
||||
tabIndex = { 0 } >
|
||||
<Icon
|
||||
className = { 'add-background' }
|
||||
size = { 20 }
|
||||
src = { IconPlusCircle } />
|
||||
{t('virtualBackground.addBackground')}
|
||||
</label> }
|
||||
<input
|
||||
accept = 'image/*'
|
||||
className = 'file-upload-btn'
|
||||
id = 'file-upload'
|
||||
onChange = { uploadImage }
|
||||
ref = { uploadImageButton }
|
||||
type = 'file' />
|
||||
{_showUploadButton
|
||||
&& <UploadImageButton
|
||||
setLoading = { setLoading }
|
||||
setOptions = { setOptions }
|
||||
setStoredImages = { setStoredImages }
|
||||
showLabel = { previewIsLoaded }
|
||||
storedImages = { storedImages } />}
|
||||
<div
|
||||
className = 'virtual-background-dialog'
|
||||
role = 'radiogroup'
|
||||
|
@ -535,7 +457,7 @@ function VirtualBackground({
|
|||
src = { IconShareDesktop } />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{images.map(image => (
|
||||
{_images.map(image => (
|
||||
<Tooltip
|
||||
content = { image.tooltip && t(`virtualBackground.${image.tooltip}`) }
|
||||
key = { image.id }
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// @flow
|
||||
|
||||
/**
|
||||
* An enumeration of the different virtual background types.
|
||||
*
|
||||
|
@ -9,3 +11,54 @@ export const VIRTUAL_BACKGROUND_TYPE = {
|
|||
BLUR: 'blur',
|
||||
NONE: 'none'
|
||||
};
|
||||
|
||||
|
||||
export type Image = {
|
||||
tooltip?: string,
|
||||
id: string,
|
||||
src: string
|
||||
}
|
||||
|
||||
// The limit of virtual background uploads is 24. When the number
|
||||
// of uploads is 25 we trigger the deleteStoredImage function to delete
|
||||
// the first/oldest uploaded background.
|
||||
export const BACKGROUNDS_LIMIT = 25;
|
||||
|
||||
|
||||
export const IMAGES: Array<Image> = [
|
||||
{
|
||||
tooltip: 'image1',
|
||||
id: '1',
|
||||
src: 'images/virtual-background/background-1.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image2',
|
||||
id: '2',
|
||||
src: 'images/virtual-background/background-2.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image3',
|
||||
id: '3',
|
||||
src: 'images/virtual-background/background-3.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image4',
|
||||
id: '4',
|
||||
src: 'images/virtual-background/background-4.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image5',
|
||||
id: '5',
|
||||
src: 'images/virtual-background/background-5.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image6',
|
||||
id: '6',
|
||||
src: 'images/virtual-background/background-6.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image7',
|
||||
id: '7',
|
||||
src: 'images/virtual-background/background-7.jpg'
|
||||
}
|
||||
];
|
||||
|
|
Loading…
Reference in New Issue