You have not enabled video
diff --git a/frontend/src/components/BackgroundEffects/EffectOptionButtons/EffectOptionButtons.tsx b/frontend/src/components/BackgroundEffects/EffectOptionButtons/EffectOptionButtons.tsx
index 7b747898..a9fb025a 100644
--- a/frontend/src/components/BackgroundEffects/EffectOptionButtons/EffectOptionButtons.tsx
+++ b/frontend/src/components/BackgroundEffects/EffectOptionButtons/EffectOptionButtons.tsx
@@ -4,9 +4,13 @@ import BlurOnIcon from '@mui/icons-material/BlurOn';
import SelectableOption from '../SelectableOption';
const options = [
- { key: 'none', icon:
},
- { key: 'low-blur', icon:
},
- { key: 'high-blur', icon:
},
+ { key: 'none', icon:
, name: 'Remove background' },
+ { key: 'low-blur', icon:
, name: 'Slightly background blur' },
+ {
+ key: 'high-blur',
+ icon:
,
+ name: 'Strong background blur',
+ },
];
export type EffectOptionButtonsProps = {
@@ -29,10 +33,11 @@ const EffectOptionButtons = ({
}: EffectOptionButtonsProps): ReactElement => {
return (
<>
- {options.map(({ key, icon }) => (
+ {options.map(({ key, icon, name }) => (
setBackgroundSelected(key)}
icon={icon}
diff --git a/frontend/src/components/BackgroundEffects/FileUploader/FileUploader.spec.tsx b/frontend/src/components/BackgroundEffects/FileUploader/FileUploader.spec.tsx
new file mode 100644
index 00000000..fb9fb47d
--- /dev/null
+++ b/frontend/src/components/BackgroundEffects/FileUploader/FileUploader.spec.tsx
@@ -0,0 +1,43 @@
+import { render, screen, fireEvent } from '@testing-library/react';
+import { it, vi, describe, expect } from 'vitest';
+import FileUploader from './FileUploader';
+
+describe('FileUploader', () => {
+ it('renders upload UI', () => {
+ render();
+ expect(screen.getByText(/Drag and Drop, or click here to upload Image/i)).toBeInTheDocument();
+ expect(screen.getByTestId('file-upload-input')).toBeInTheDocument();
+ expect(screen.getByTestId('file-upload-drop-area')).toBeInTheDocument();
+ });
+
+ it('handles file input change', () => {
+ const handleFileChange = vi.fn();
+ render();
+ const input = screen.getByTestId('file-upload-input');
+ const file = new File(['dummy'], 'test.png', { type: 'image/png' });
+ fireEvent.change(input, { target: { files: [file] } });
+ expect(handleFileChange).toHaveBeenCalled();
+ });
+
+ it('handles file drop event', () => {
+ const handleFileChange = vi.fn();
+ render();
+ const box = screen.getByTestId('file-upload-drop-area');
+ const file = new File(['dummy'], 'test.jpg', { type: 'image/jpeg' });
+ const dataTransfer = {
+ files: [file],
+ clearData: vi.fn(),
+ };
+ fireEvent.drop(box, { dataTransfer });
+ expect(handleFileChange).toHaveBeenCalledWith({ target: { files: [file] } });
+ });
+
+ it('shows drag over style when dragging', () => {
+ render();
+ const box = screen.getByTestId('file-upload-drop-area');
+ fireEvent.dragOver(box);
+ expect(box).toHaveStyle('border: 2px dashed #1976d2');
+ fireEvent.dragLeave(box);
+ expect(box).toHaveStyle('border: 1px dashed #C1C1C1');
+ });
+});
diff --git a/frontend/src/components/BackgroundEffects/FileUploader/FileUploader.tsx b/frontend/src/components/BackgroundEffects/FileUploader/FileUploader.tsx
new file mode 100644
index 00000000..6ea155d6
--- /dev/null
+++ b/frontend/src/components/BackgroundEffects/FileUploader/FileUploader.tsx
@@ -0,0 +1,81 @@
+import { ChangeEvent, useState, DragEvent, ReactElement } from 'react';
+import { Box, Typography } from '@mui/material';
+import CloudUploadIcon from '@mui/icons-material/CloudUpload';
+
+export type FileUploaderProps = {
+ handleFileChange: (
+ event: ChangeEvent | { target: { files: FileList } }
+ ) => void;
+};
+
+/**
+ * FileUploader component allows users to upload image files via drag-and-drop or file selection.
+ *
+ * This component manages the UI for adding background effects.
+ * @param {FileUploaderProps} props - The props for the component.
+ * @property {Function} handleFileChange - Callback function to handle background image change.
+ * @returns {ReactElement} The add background effect layout component.
+ */
+const FileUploader = ({ handleFileChange }: FileUploaderProps): ReactElement => {
+ const [dragOver, setDragOver] = useState(false);
+
+ const onDragOver = (e: DragEvent) => {
+ e.preventDefault();
+ setDragOver(true);
+ };
+
+ const onDragLeave = (e: DragEvent) => {
+ e.preventDefault();
+ setDragOver(false);
+ };
+
+ const onDrop = (e: DragEvent) => {
+ e.preventDefault();
+ setDragOver(false);
+ const { files } = e.dataTransfer;
+ if (files && files.length > 0) {
+ handleFileChange({ target: { files } });
+ e.dataTransfer.clearData();
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default FileUploader;
diff --git a/frontend/src/components/BackgroundEffects/FileUploader/index.tsx b/frontend/src/components/BackgroundEffects/FileUploader/index.tsx
new file mode 100644
index 00000000..9036fc89
--- /dev/null
+++ b/frontend/src/components/BackgroundEffects/FileUploader/index.tsx
@@ -0,0 +1,3 @@
+import FileUploader from './FileUploader';
+
+export default FileUploader;
diff --git a/frontend/src/components/BackgroundEffects/SelectableOption/SelectableOption.spec.tsx b/frontend/src/components/BackgroundEffects/SelectableOption/SelectableOption.spec.tsx
index 798ff130..f35a0dfa 100644
--- a/frontend/src/components/BackgroundEffects/SelectableOption/SelectableOption.spec.tsx
+++ b/frontend/src/components/BackgroundEffects/SelectableOption/SelectableOption.spec.tsx
@@ -60,4 +60,32 @@ describe('SelectableOption', () => {
const option = screen.getByTestId('background-disabled');
expect(option).toHaveAttribute('aria-disabled', 'true');
});
+
+ it('shows the title in the tooltip', async () => {
+ render(
+ {}}
+ id="with-title"
+ icon={Icon}
+ title="My Tooltip Title"
+ />
+ );
+ await userEvent.hover(screen.getByTestId('background-with-title'));
+ expect(screen.getByLabelText('My Tooltip Title')).toBeInTheDocument();
+ });
+
+ it('renders children inside the option', () => {
+ render(
+ {}}
+ id="with-children"
+ icon={Icon}
+ >
+ Child Content
+
+ );
+ expect(screen.getByTestId('child-content')).toBeInTheDocument();
+ });
});
diff --git a/frontend/src/components/BackgroundEffects/SelectableOption/SelectableOption.tsx b/frontend/src/components/BackgroundEffects/SelectableOption/SelectableOption.tsx
index 231512ae..73581599 100644
--- a/frontend/src/components/BackgroundEffects/SelectableOption/SelectableOption.tsx
+++ b/frontend/src/components/BackgroundEffects/SelectableOption/SelectableOption.tsx
@@ -1,5 +1,5 @@
import { ReactElement, ReactNode } from 'react';
-import { Paper } from '@mui/material';
+import { Box, Paper, Tooltip } from '@mui/material';
import { DEFAULT_SELECTABLE_OPTION_WIDTH } from '../../../utils/constants';
export type SelectableOptionProps = {
@@ -7,9 +7,11 @@ export type SelectableOptionProps = {
onClick: () => void;
id: string;
icon?: ReactNode;
+ title?: string;
image?: string;
size?: number;
isDisabled?: boolean;
+ children?: ReactNode;
};
/**
@@ -21,9 +23,11 @@ export type SelectableOptionProps = {
* @property {Function} onClick - Function to call when the option is clicked
* @property {string} id - Unique identifier for the option
* @property {ReactNode} icon - Icon to display in the option
+ * @property {string} title - Title to display in the option
* @property {string} image - Image URL to display in the option
* @property {number} size - Size of the option (default is DEFAULT_SELECTABLE_OPTION_WIDTH)
* @property {boolean} isDisabled - Whether the option is disabled
+ * @property {ReactNode} children - Additional content to render inside the option
* @returns {ReactElement} A selectable option element
*/
const SelectableOption = ({
@@ -31,44 +35,60 @@ const SelectableOption = ({
onClick,
id,
icon,
+ title,
image,
size = DEFAULT_SELECTABLE_OPTION_WIDTH,
isDisabled = false,
+ children,
...otherProps // Used by MUI Tooltip
}: SelectableOptionProps): ReactElement => {
return (
-
- {image ? (
-
- ) : (
- icon
- )}
-
+
+
+ {image ? (
+
+ ) : (
+ icon
+ )}
+ {children}
+
+
+
);
};
diff --git a/frontend/src/components/MeetingRoom/BackgroundEffectsLayout/BackgroundEffectsLayout.spec.tsx b/frontend/src/components/MeetingRoom/BackgroundEffectsLayout/BackgroundEffectsLayout.spec.tsx
index 7a1a70d2..17d6ffad 100644
--- a/frontend/src/components/MeetingRoom/BackgroundEffectsLayout/BackgroundEffectsLayout.spec.tsx
+++ b/frontend/src/components/MeetingRoom/BackgroundEffectsLayout/BackgroundEffectsLayout.spec.tsx
@@ -37,9 +37,9 @@ describe('BackgroundEffectsLayout', () => {
expect(screen.getByTestId('right-panel-title')).toHaveTextContent('Background Effects');
expect(screen.getByTestId('background-video-container')).toBeInTheDocument();
expect(screen.getByTestId('background-none')).toBeInTheDocument();
- expect(screen.getByTestId('background-upload')).toBeInTheDocument();
expect(screen.getByTestId('background-bg1')).toBeInTheDocument();
- expect(screen.getAllByText(/Choose Background Effect/i)[0]).toBeInTheDocument();
+ expect(screen.getAllByText(/Backgrounds/i)[0]).toBeInTheDocument();
+ expect(screen.getAllByText(/Add background/i)[0]).toBeInTheDocument();
expect(screen.getByTestId('background-effect-cancel-button')).toBeInTheDocument();
expect(screen.getByTestId('background-effect-apply-button')).toBeInTheDocument();
});
diff --git a/frontend/src/components/MeetingRoom/BackgroundEffectsLayout/BackgroundEffectsLayout.tsx b/frontend/src/components/MeetingRoom/BackgroundEffectsLayout/BackgroundEffectsLayout.tsx
index 2184778c..0bba24dc 100644
--- a/frontend/src/components/MeetingRoom/BackgroundEffectsLayout/BackgroundEffectsLayout.tsx
+++ b/frontend/src/components/MeetingRoom/BackgroundEffectsLayout/BackgroundEffectsLayout.tsx
@@ -1,14 +1,13 @@
import { ReactElement, useCallback, useEffect, useState } from 'react';
-import { Box, Button, Typography } from '@mui/material';
+import { Box, Button, useMediaQuery } from '@mui/material';
import usePublisherContext from '../../../hooks/usePublisherContext';
import RightPanelTitle from '../RightPanel/RightPanelTitle';
-import EffectOptionButtons from '../../BackgroundEffects/EffectOptionButtons/EffectOptionButtons';
-import BackgroundGallery from '../../BackgroundEffects/BackgroundGallery/BackgroundGallery';
import BackgroundVideoContainer from '../../BackgroundEffects/BackgroundVideoContainer';
import useBackgroundPublisherContext from '../../../hooks/useBackgroundPublisherContext';
-import { DEFAULT_SELECTABLE_OPTION_WIDTH } from '../../../utils/constants';
-import AddBackgroundEffect from '../../BackgroundEffects/AddBackgroundEffect/AddBackgroundEffect';
import getInitialBackgroundFilter from '../../../utils/backgroundFilter/getInitialBackgroundFilter/getInitialBackgroundFilter';
+import BackgroundEffectTabs, {
+ cleanBackgroundReplacementIfSelectedAndDeleted,
+} from '../../BackgroundEffects/BackgroundEffectTabs/BackgroundEffectTabs';
export type BackgroundEffectsLayoutProps = {
handleClose: () => void;
@@ -18,7 +17,7 @@ export type BackgroundEffectsLayoutProps = {
/**
* BackgroundEffectsLayout Component
*
- * This component manages the UI for background effects (cancel background and blurs) in a room.
+ * This component manages the UI for background effects in the waiting room.
* @param {BackgroundEffectsLayoutProps} props - The props for the component.
* @property {boolean} isOpen - Whether the background effects panel is open.
* @property {Function} handleClose - Function to close the panel.
@@ -28,7 +27,9 @@ const BackgroundEffectsLayout = ({
handleClose,
isOpen,
}: BackgroundEffectsLayoutProps): ReactElement | false => {
+ const [tabSelected, setTabSelected] = useState(0);
const [backgroundSelected, setBackgroundSelected] = useState('none');
+ const isShortScreen = useMediaQuery('(max-height:825px)');
const { publisher, changeBackground, isVideoEnabled } = usePublisherContext();
const { publisherVideoElement, changeBackground: changeBackgroundPreview } =
useBackgroundPublisherContext();
@@ -43,6 +44,11 @@ const BackgroundEffectsLayout = ({
handleClose();
};
+ const customBackgroundImageChange = (dataUrl: string) => {
+ setTabSelected(0);
+ handleBackgroundSelect(dataUrl);
+ };
+
const setInitialBackgroundReplacement = useCallback(() => {
const selectedBackgroundOption = getInitialBackgroundFilter(publisher);
setBackgroundSelected(selectedBackgroundOption);
@@ -51,7 +57,6 @@ const BackgroundEffectsLayout = ({
const publisherVideoFilter = publisher?.getVideoFilter();
- // Reset background when closing the panel
useEffect(() => {
if (isOpen) {
const currentOption = setInitialBackgroundReplacement();
@@ -59,67 +64,72 @@ const BackgroundEffectsLayout = ({
}
}, [publisherVideoFilter, isOpen, changeBackgroundPreview, setInitialBackgroundReplacement]);
- return (
- isOpen && (
- <>
-
+ if (!isOpen) {
+ return false;
+ }
-
-
-
+ return (
+
+
-
-
- Choose Background Effect
-
+
+
+
-
-
-
- {/* TODO: load custom images */}
-
-
-
+
+ cleanBackgroundReplacementIfSelectedAndDeleted(
+ publisher,
+ changeBackground,
+ backgroundSelected,
+ dataUrl
+ )
+ }
+ customBackgroundImageChange={customBackgroundImageChange}
+ />
-
-
-
-
- >
- )
+
+
+
+
+
);
};
diff --git a/frontend/src/components/MeetingRoom/DeviceSettingsMenu/DeviceSettingsMenu.tsx b/frontend/src/components/MeetingRoom/DeviceSettingsMenu/DeviceSettingsMenu.tsx
index a3eac284..0ca4d46c 100644
--- a/frontend/src/components/MeetingRoom/DeviceSettingsMenu/DeviceSettingsMenu.tsx
+++ b/frontend/src/components/MeetingRoom/DeviceSettingsMenu/DeviceSettingsMenu.tsx
@@ -55,6 +55,11 @@ const DeviceSettingsMenu = ({
const theme = useTheme();
const customLightBlueColor = 'rgb(138, 180, 248)';
+ const handleToggleBackgroundEffects = () => {
+ toggleBackgroundEffects();
+ handleToggle();
+ };
+
useDropdownResizeObserver({ setIsOpen, dropDownRefElement: anchorRef.current });
const renderSettingsMenu = () => {
@@ -74,7 +79,7 @@ const DeviceSettingsMenu = ({
{hasMediaProcessorSupport() && (
<>
-
+
>
)}
>
diff --git a/frontend/src/components/WaitingRoom/BackgroundEffects/BackgroundEffectsDialog/BackgroundEffectsDialog.tsx b/frontend/src/components/WaitingRoom/BackgroundEffects/BackgroundEffectsDialog/BackgroundEffectsDialog.tsx
index b19327c5..414cc50e 100644
--- a/frontend/src/components/WaitingRoom/BackgroundEffects/BackgroundEffectsDialog/BackgroundEffectsDialog.tsx
+++ b/frontend/src/components/WaitingRoom/BackgroundEffects/BackgroundEffectsDialog/BackgroundEffectsDialog.tsx
@@ -1,4 +1,5 @@
-import { Dialog, DialogContent } from '@mui/material';
+import { Dialog, DialogContent, DialogTitle, IconButton } from '@mui/material';
+import CloseIcon from '@mui/icons-material/Close';
import { ReactElement } from 'react';
import BackgroundEffectsLayout from '../BackgroundEffectsLayout/BackgroundEffectsLayout';
@@ -20,18 +21,29 @@ const BackgroundEffectsDialog = ({
isBackgroundEffectsOpen,
setIsBackgroundEffectsOpen,
}: BackgroundEffectsDialogProps): ReactElement | false => {
+ const handleClose = () => {
+ setIsBackgroundEffectsOpen(false);
+ };
+
return (
-