Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
3841fd7
initial background replacement
OscarFava Jul 21, 2025
55af7e2
Modify folder structure + add background video container
OscarFava Jul 21, 2025
506acb5
Implemented background replacement and blur effects for video calls, …
OscarFava Jul 23, 2025
8f359b0
Refactored background effects for better layout and responsiveness, a…
OscarFava Jul 25, 2025
45020d0
Add custom background images initial button
OscarFava Jul 29, 2025
f60b121
Minor fix
OscarFava Jul 29, 2025
0b5e785
Remove blurButton
OscarFava Jul 29, 2025
d40aa17
unit testing
OscarFava Jul 29, 2025
3a51e0e
removed
OscarFava Jul 29, 2025
21077e5
Add unit testing
OscarFava Jul 30, 2025
7570983
Fix unit testing and first review (clean code and add comments)
OscarFava Jul 31, 2025
ceffe18
Merge remote-tracking branch 'origin/develop' into vidsol-105/backgro…
OscarFava Jul 31, 2025
a9ba4d2
Remove unfound file
OscarFava Jul 31, 2025
310abf1
Reduce duplicated lines
OscarFava Aug 1, 2025
cfff835
Reduce duplicated lines
OscarFava Aug 1, 2025
d36d700
Fix unit testing
OscarFava Aug 1, 2025
2639977
Clean code and give appropiate names
OscarFava Aug 1, 2025
965c1fe
Improve reliability
OscarFava Aug 1, 2025
b7c582b
Fix lint
OscarFava Aug 1, 2025
835c2b5
Fix kint
OscarFava Aug 1, 2025
714c562
Fix testing
OscarFava Aug 1, 2025
3d1af88
Fix integration
OscarFava Aug 1, 2025
574ac90
Fix unit testing + clean code + Fix docs typo + minor fixes
OscarFava Aug 4, 2025
9f5e3f4
Fix unit testing + add doc + minor bug fixs
OscarFava Aug 5, 2025
8092cdc
Tabs layout
OscarFava Aug 6, 2025
75f145e
Improve CSS responsive
OscarFava Aug 6, 2025
3afadd5
Start 'Add custom background effects layout'
OscarFava Aug 6, 2025
651cff2
Fix unit testing + add doc + minor bug fixs
OscarFava Aug 7, 2025
6b7031f
Clean code
OscarFava Aug 18, 2025
8f9211b
Merge branch 'vidsol-105/background-replacement' into vidsol-105/back…
OscarFava Aug 18, 2025
54081f6
Improve visibility of add background + store images on LocalStorage a…
OscarFava Aug 18, 2025
05b808e
Tooltip in add background + automatic apply when uploading image + cl…
OscarFava Aug 20, 2025
cf7502d
Tooltip in backgrounds boxes + align cancel and apply buttons margins…
OscarFava Aug 21, 2025
e4b58ec
Add cross to close dialog + improve responsive layout
OscarFava Aug 21, 2025
72bf9ac
Merge
OscarFava Aug 21, 2025
f5e4392
Add comment
OscarFava Aug 21, 2025
baa4ce2
Fix unit testing and clean code
OscarFava Aug 21, 2025
9287c09
Fix crypto and duplicated code
OscarFava Aug 21, 2025
dfa5506
Fix test
OscarFava Aug 21, 2025
8650c99
Reduce duplicated code
OscarFava Aug 21, 2025
973d82d
Reduce duplicated code
OscarFava Aug 21, 2025
6fc8c7e
Merge remote-tracking branch 'origin/develop' into vidsol-105/backgro…
OscarFava Sep 12, 2025
03cf685
Add margin to not enabled video
OscarFava Sep 12, 2025
001ce74
Minor fixes, move constants and add missing JSDOC
OscarFava Sep 15, 2025
b3b55fa
Fix typo
OscarFava Sep 15, 2025
97e2a71
Fix testing
OscarFava Sep 15, 2025
f0531fc
Clean code and fix tests
OscarFava Sep 16, 2025
628315c
Fix warning
OscarFava Sep 16, 2025
fac9129
Merge remote-tracking branch 'origin/develop' into vidsol-105/backgro…
OscarFava Sep 16, 2025
aabeab8
Fix bug
OscarFava Sep 16, 2025
314a1ff
Merge remote-tracking branch 'origin/develop' into vidsol-105/backgro…
OscarFava Sep 19, 2025
cb26d88
Unify Background Effects Layout
OscarFava Sep 19, 2025
89c163a
Merge remote-tracking branch 'origin/develop' into vidsol-105/backgro…
OscarFava Sep 22, 2025
b18b150
Fix testing and minor bugs
OscarFava Sep 22, 2025
18dd322
Improve CSS and give shorter names
OscarFava Sep 23, 2025
ad998b1
Merge remote-tracking branch 'origin/develop' into vidsol-105/backgro…
OscarFava Sep 26, 2025
81fa0d3
Update image upload validation to include GIF and BMP formats; refact…
OscarFava Sep 29, 2025
27820f6
lint fix
OscarFava Sep 29, 2025
1e07403
Refactor background effect descriptions, improve file size error mess…
OscarFava Sep 30, 2025
c53c6b0
missing test
OscarFava Oct 1, 2025
6fb542b
Update README
OscarFava Oct 1, 2025
4247f58
Update README
OscarFava Oct 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,14 @@ This application provides features for common conferencing use cases, such as:
<summary>Input and output device selectors.</summary>
<img src="docs/assets/DeviceSelector.png" alt="Screenshot of audio devices selector">
</details>
- Background blur and noise suppression toggles.
- <details>
<summary>Noise suppression toggles in meeting room</summary>
<img src="docs/assets/NoiseSupression.png" alt="Screenshot of noise supression toggle">
</details>
- <details>
<summary>Background effects in meeting and waiting room. You can set predefined images, custom image or slight/strong background blur. Images can be uploaded from local device or URL in these formats: JPG, PNG, GIF or BMP.</summary>
<img src="docs/assets/BGEffects.png" alt="Screenshot of background effects">
</details>
- <details>
<summary>Composed archiving capabilities to record your meetings.</summary>
<img src="docs/assets/Archiving.png" alt="Screenshot of archiving dialog box">
Expand Down
Binary file added docs/assets/BGEffects.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/NoiseSupression.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { vi, describe, it, expect, beforeAll } from 'vitest';
import AddBackgroundEffectLayout from './AddBackgroundEffectLayout';

vi.mock('../../../../utils/useImageStorage/useImageStorage', () => ({
__esModule: true,
default: () => ({
storageError: '',
handleImageFromFile: vi.fn(async () => ({
dataUrl: 'data:image/png;base64,MOCKED',
})),
handleImageFromLink: vi.fn(async () => ({
dataUrl: 'data:image/png;base64,MOCKED_LINK',
})),
}),
}));

describe('AddBackgroundEffectLayout', () => {
const cb = vi.fn();

beforeAll(() => {
vi.clearAllMocks();
});

it('should render', () => {
render(<AddBackgroundEffectLayout customBackgroundImageChange={vi.fn()} />);
expect(screen.getByText(/Drag and drop, or click here to upload image/i)).toBeInTheDocument();
expect(screen.getByPlaceholderText(/Link from the web/i)).toBeInTheDocument();
expect(screen.getByTestId('background-effect-link-submit-button')).toBeInTheDocument();
});

it('shows error for invalid file type', async () => {
render(<AddBackgroundEffectLayout customBackgroundImageChange={vi.fn()} />);
const input = screen.getByLabelText(/upload/i);
const file = new File(['dummy'], 'test.txt', { type: 'text/plain' });
fireEvent.change(input, { target: { files: [file] } });
expect(
await screen.findByText(/Only JPG, PNG, GIF, or BMP images are allowed/i)
).toBeInTheDocument();
});

it('shows error for file size too large', async () => {
render(<AddBackgroundEffectLayout customBackgroundImageChange={vi.fn()} />);
const input = screen.getByLabelText(/upload/i);
const file = new File(['x'.repeat(3 * 1024 * 1024)], 'big.png', { type: 'image/png' });
Object.defineProperty(file, 'size', { value: 3 * 1024 * 1024 });
fireEvent.change(input, { target: { files: [file] } });
expect(await screen.findByText(/Image must be less than 2MB/i)).toBeInTheDocument();
});

it('handles valid image file upload', async () => {
render(<AddBackgroundEffectLayout customBackgroundImageChange={cb} />);
const input = screen.getByLabelText(/upload/i);
const file = new File(['dummy'], 'test.png', { type: 'image/png' });
fireEvent.change(input, { target: { files: [file] } });
await waitFor(() => expect(cb).toHaveBeenCalledWith('data:image/png;base64,MOCKED'));
});

it('handles valid link submit', async () => {
render(<AddBackgroundEffectLayout customBackgroundImageChange={cb} />);
const input = screen.getByPlaceholderText(/Link from the web/i);
fireEvent.change(input, { target: { value: 'https://example.com/image.png' } });
const button = screen.getByTestId('background-effect-link-submit-button');
fireEvent.click(button);
await waitFor(() => expect(cb).toHaveBeenCalledWith('data:image/png;base64,MOCKED_LINK'));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import {
Box,
Button,
CircularProgress,
InputAdornment,
TextField,
Typography,
} from '@mui/material';
import { ChangeEvent, ReactElement, useState } from 'react';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import LinkIcon from '@mui/icons-material/Link';
import FileUploader from '../../FileUploader/FileUploader';
import { ALLOWED_TYPES, MAX_SIZE_MB } from '../../../../utils/constants';
import useImageStorage from '../../../../utils/useImageStorage/useImageStorage';

export type AddBackgroundEffectLayoutProps = {
customBackgroundImageChange: (dataUrl: string) => void;
};

/**
* AddBackgroundEffectLayout Component
*
* This component manages the UI for adding background effects.
* @param {AddBackgroundEffectLayoutProps} props - The props for the component.
* @property {Function} customBackgroundImageChange - Callback function to handle background image change.
* @returns {ReactElement} The add background effect layout component.
*/
const AddBackgroundEffectLayout = ({
customBackgroundImageChange,
}: AddBackgroundEffectLayoutProps): ReactElement => {
const [fileError, setFileError] = useState<string>('');
const [imageLink, setImageLink] = useState<string>('');
const [linkLoading, setLinkLoading] = useState<boolean>(false);
const { storageError, handleImageFromFile, handleImageFromLink } = useImageStorage();

type HandleFileChangeType = ChangeEvent<HTMLInputElement> | { target: { files: FileList } };

const handleFileChange = async (e: HandleFileChangeType) => {
const { files } = e.target;
if (!files || files.length === 0) {
return;
}

const file = files[0];
if (!file) {
return;
}

if (!ALLOWED_TYPES.includes(file.type)) {
setFileError('Only JPG, PNG, GIF, or BMP images are allowed.');
return;
}

if (file.size > MAX_SIZE_MB * 1024 * 1024) {
setFileError(`Image must be less than ${MAX_SIZE_MB}MB.`);
return;
}

try {
const newImage = await handleImageFromFile(file);
if (newImage) {
setFileError('');
customBackgroundImageChange(newImage.dataUrl);
}
} catch {
setFileError('Failed to process uploaded image.');
}
};

const handleLinkSubmit = async () => {
setFileError('');
setLinkLoading(true);
try {
const newImage = await handleImageFromLink(imageLink);
if (newImage) {
setFileError('');
customBackgroundImageChange(newImage.dataUrl);
} else {
setFileError('Failed to store image.');
}
} catch {
// error handled in hook
} finally {
setLinkLoading(false);
}
};

return (
<Box
sx={{
overflow: 'auto',
}}
>
<FileUploader handleFileChange={handleFileChange} />

{(fileError || storageError) && (
<Typography color="error" mt={1} fontSize={14}>
{fileError || storageError}
</Typography>
)}

<Box mt={2} display="flex" alignItems="center" gap={1}>
<TextField
fullWidth
size="small"
placeholder="Link from the web"
className="add-background-effect-input"
value={imageLink}
onChange={(e) => setImageLink(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
{linkLoading ? <CircularProgress size={24} /> : <LinkIcon sx={{ fontSize: 24 }} />}
</InputAdornment>
),
}}
/>

<Button
data-testid="background-effect-link-submit-button"
variant="contained"
color="primary"
onClick={handleLinkSubmit}
disabled={linkLoading}
style={{ minWidth: 0, padding: '8px 12px' }}
>
{linkLoading ? (
<CircularProgress size={24} color="inherit" />
) : (
<ArrowForwardIcon sx={{ fontSize: 24 }} />
)}
</Button>
</Box>
</Box>
);
};

export default AddBackgroundEffectLayout;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import AddBackgroundEffectLayout from './AddBackgroundEffectLayout';

export default AddBackgroundEffectLayout;

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi, describe, it, expect } from 'vitest';
import BackgroundEffectTabs from './BackgroundEffectTabs';

describe('BackgroundEffectTabs', () => {
const setTabSelected = vi.fn();
const setBackgroundSelected = vi.fn();
const clearBgWhenSelectedDeleted = vi.fn();
const customBackgroundImageChange = vi.fn();

it('renders tabs and defaults to Backgrounds tab', () => {
render(
<BackgroundEffectTabs
mode="meeting"
tabSelected={0}
setTabSelected={setTabSelected}
backgroundSelected=""
setBackgroundSelected={setBackgroundSelected}
cleanupSelectedBackgroundReplacement={clearBgWhenSelectedDeleted}
customBackgroundImageChange={customBackgroundImageChange}
/>
);
expect(screen.getByRole('tab', { name: /Backgrounds/i })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: /Add Background/i })).toBeInTheDocument();
expect(screen.getByRole('tab', { name: /Backgrounds/i })).toHaveAttribute(
'aria-selected',
'true'
);
});

it('switches to Add Background tab when clicked', async () => {
render(
<BackgroundEffectTabs
mode="meeting"
tabSelected={0}
setTabSelected={setTabSelected}
backgroundSelected=""
setBackgroundSelected={setBackgroundSelected}
cleanupSelectedBackgroundReplacement={clearBgWhenSelectedDeleted}
customBackgroundImageChange={customBackgroundImageChange}
/>
);
const addTab = screen.getByRole('tab', { name: /Add Background/i });
await userEvent.click(addTab);
expect(setTabSelected).toHaveBeenCalledWith(1);
});

it('renders AddBackgroundEffectLayout when Add Background tab is selected', () => {
render(
<BackgroundEffectTabs
mode="waiting"
tabSelected={1}
setTabSelected={setTabSelected}
backgroundSelected=""
setBackgroundSelected={setBackgroundSelected}
cleanupSelectedBackgroundReplacement={clearBgWhenSelectedDeleted}
customBackgroundImageChange={customBackgroundImageChange}
/>
);
expect(screen.getByText(/upload/i)).toBeInTheDocument();
});
});
Loading
Loading