Skip to content

Commit

Permalink
Finalize grid mode (#699)
Browse files Browse the repository at this point in the history
  • Loading branch information
olipyskoty committed Jun 10, 2022
1 parent 05ff339 commit b913d5e
Show file tree
Hide file tree
Showing 25 changed files with 337 additions and 210 deletions.
9 changes: 8 additions & 1 deletion cypress/integration/twilio-video.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ const getRoomName = () =>
context('A video app user', () => {
describe('before entering a room', () => {
it('should see their audio level indicator moving in the media device panel', () => {
cy.visit('/');
// These tests were written before Grid View was implemented. This app now activates
// Grid View by default, so here we activate Presentation View before visiting the app so
// that the tests can pass.
cy.visit('/', {
onBeforeLoad: window => {
window.localStorage.setItem('grid-view-active-key', false);
},
});

cy.get('#input-user-name').type('testuser');
cy.get('#input-room-name').type(getRoomName());
Expand Down
8 changes: 8 additions & 0 deletions cypress/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ module.exports = (on, config) => {
args,
});
const page = (participants[name] = await browser.newPage()); // keep track of this participant for future use

// These tests were written before Grid View was implemented. This app now activates
// Grid View by default, so here we activate Presentation View before visiting the app so
// that the tests can pass.
await page.evaluateOnNewDocument(() => {
localStorage.clear();
localStorage.setItem('grid-view-active-key', false);
});
await page.goto(config.baseUrl);
await page.type('#input-user-name', name);
await page.type('#input-room-name', roomName);
Expand Down
9 changes: 8 additions & 1 deletion cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import detectSound from './detectSound';

Cypress.Commands.add('joinRoom', (username, roomname) => {
cy.visit('/');
// These tests were written before Grid View was implemented. This app now activates
// Grid View by default, so here we activate Presentation View before visiting the app so
// that the tests can pass.
cy.visit('/', {
onBeforeLoad: window => {
window.localStorage.setItem('grid-view-active-key', false);
},
});
cy.get('#input-user-name').type(username);
cy.get('#input-room-name').type(roomname);
cy.get('[type="submit"]').click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { FormControl, MenuItem, Typography, Select, Grid } from '@material-ui/core';
import { useAppState } from '../../../state';

const MAX_PARTICIPANT_OPTIONS = [9, 16, 25, 36, 49];
const MAX_PARTICIPANT_OPTIONS = [4, 9, 16, 25];

export default function MaxGridParticipants() {
const { maxGridParticipants, setMaxGridParticipants } = useAppState();
Expand Down
12 changes: 6 additions & 6 deletions src/components/GridView/GridView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { GridView } from './GridView';
import { shallow } from 'enzyme';
import useGridLayout from '../../hooks/useGridLayout/useGridLayout';
import { useAppState } from '../../state';
import { usePagination } from '../../hooks/usePagination/usePagination';
import { usePagination } from './usePagination/usePagination';

const mockLocalParticipant = { identity: 'test-local-participant', sid: 0 };
const mockParticipants = [
Expand All @@ -14,10 +14,10 @@ const mockParticipants = [
];

jest.mock('../../constants', () => ({
GRID_MODE_ASPECT_RATIO: 9 / 16,
GRID_MODE_MARGIN: 3,
GRID_VIEW_ASPECT_RATIO: 9 / 16,
GRID_VIEW_MARGIN: 3,
}));
jest.mock('../../hooks/useCollaborationParticipants/useCollaborationParticipants', () => () => mockParticipants);
jest.mock('../../hooks/usePresentationParticipants/usePresentationParticipants', () => () => mockParticipants);
jest.mock('../../hooks/useVideoContext/useVideoContext', () => () => ({
room: {
localParticipant: mockLocalParticipant,
Expand All @@ -30,7 +30,7 @@ jest.mock('../../hooks/useGridLayout/useGridLayout', () =>
}))
);

jest.mock('../../hooks/usePagination/usePagination', () => ({
jest.mock('./usePagination/usePagination', () => ({
usePagination: jest.fn(() => ({
currentPage: 2,
totalPages: 4,
Expand All @@ -50,7 +50,7 @@ describe('the GridView component', () => {
it('should render correctly', () => {
const wrapper = shallow(<GridView />);
expect(wrapper).toMatchSnapshot();
expect(useGridLayout).toHaveBeenCalledWith(5);
expect(useGridLayout).toHaveBeenCalledWith(9);
});

it('should not render the previous page button when the user is viewing the first page', () => {
Expand Down
130 changes: 68 additions & 62 deletions src/components/GridView/GridView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,85 @@ import React from 'react';
import ArrowBack from '@material-ui/icons/ArrowBack';
import ArrowForward from '@material-ui/icons/ArrowForward';
import clsx from 'clsx';
import { GRID_MODE_ASPECT_RATIO, GRID_MODE_MARGIN } from '../../constants';
import { IconButton, makeStyles } from '@material-ui/core';
import { GRID_VIEW_ASPECT_RATIO, GRID_VIEW_MARGIN } from '../../constants';
import { IconButton, makeStyles, createStyles, Theme } from '@material-ui/core';
import { Pagination } from '@material-ui/lab';
import Participant from '../Participant/Participant';
import useGridLayout from '../../hooks/useGridLayout/useGridLayout';
import useVideoContext from '../../hooks/useVideoContext/useVideoContext';
import { usePagination } from '../../hooks/usePagination/usePagination';
import { usePagination } from './usePagination/usePagination';
import useDominantSpeaker from '../../hooks/useDominantSpeaker/useDominantSpeaker';
import useGridParticipants from '../../hooks/useGridParticipants/useGridParticipants';
import { useAppState } from '../../state';

const CONTAINER_GUTTER = '50px';

const useStyles = makeStyles({
container: {
position: 'relative',
gridArea: '1 / 1 / 2 / 3',
},
participantContainer: {
position: 'absolute',
display: 'flex',
top: CONTAINER_GUTTER,
right: CONTAINER_GUTTER,
bottom: CONTAINER_GUTTER,
left: CONTAINER_GUTTER,
margin: '0 auto',
alignContent: 'center',
flexWrap: 'wrap',
justifyContent: 'center',
},
buttonContainer: {
position: 'absolute',
top: 0,
bottom: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
const useStyles = makeStyles((theme: Theme) =>
createStyles({
container: {
background: theme.gridViewBackgroundColor,
position: 'relative',
gridArea: '1 / 1 / 2 / 3',
},
participantContainer: {
position: 'absolute',
display: 'flex',
top: CONTAINER_GUTTER,
right: CONTAINER_GUTTER,
bottom: CONTAINER_GUTTER,
left: CONTAINER_GUTTER,
margin: '0 auto',
alignContent: 'center',
flexWrap: 'wrap',
justifyContent: 'center',
},
buttonContainer: {
position: 'absolute',
top: 0,
bottom: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},

buttonContainerLeft: {
right: `calc(100% - ${CONTAINER_GUTTER})`,
left: 0,
},
buttonContainerRight: {
right: 0,
left: `calc(100% - ${CONTAINER_GUTTER})`,
},
pagination: {
'& .MuiPaginationItem-root': {
color: 'white',
buttonContainerLeft: {
right: `calc(100% - ${CONTAINER_GUTTER})`,
left: 0,
},
buttonContainerRight: {
right: 0,
left: `calc(100% - ${CONTAINER_GUTTER})`,
},
pagination: {
'& .MuiPaginationItem-root': {
color: 'white',
},
},
paginationButton: {
color: 'black',
background: 'rgba(255, 255, 255, 0.8)',
width: '40px',
height: '40px',
'&:hover': {
background: 'rgba(255, 255, 255)',
},
},
},
paginationButton: {
color: 'black',
background: 'rgba(255, 255, 255, 0.8)',
width: '40px',
height: '40px',
'&:hover': {
background: 'rgba(255, 255, 255)',
paginationContainer: {
position: 'absolute',
top: `calc(100% - ${CONTAINER_GUTTER})`,
right: 0,
bottom: 0,
left: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
},
paginationContainer: {
position: 'absolute',
top: `calc(100% - ${CONTAINER_GUTTER})`,
right: 0,
bottom: 0,
left: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
});
})
);

export function GridView() {
const classes = useStyles();
const { maxGridParticipants } = useAppState();
const { room } = useVideoContext();
const gridParticipants = useGridParticipants();
const dominantSpeaker = useDominantSpeaker(true);
Expand All @@ -85,10 +90,11 @@ export function GridView() {
...gridParticipants,
]);

const { participantVideoWidth, containerRef } = useGridLayout(paginatedParticipants.length);
const gridLayoutParticipantCount = currentPage === 1 ? paginatedParticipants.length : maxGridParticipants;
const { participantVideoWidth, containerRef } = useGridLayout(gridLayoutParticipantCount);

const participantWidth = `${participantVideoWidth}px`;
const participantHeight = `${Math.floor(participantVideoWidth * GRID_MODE_ASPECT_RATIO)}px`;
const participantHeight = `${Math.floor(participantVideoWidth * GRID_VIEW_ASPECT_RATIO)}px`;

return (
<div className={classes.container}>
Expand Down Expand Up @@ -125,7 +131,7 @@ export function GridView() {
{paginatedParticipants.map(participant => (
<div
key={participant.sid}
style={{ width: participantWidth, height: participantHeight, margin: GRID_MODE_MARGIN }}
style={{ width: participantWidth, height: participantHeight, margin: GRID_VIEW_MARGIN }}
>
<Participant
participant={participant}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { useAppState } from '../../state';
import { useAppState } from '../../../state';
import { usePagination } from './usePagination';

jest.mock('../../state');
jest.mock('../../../state');

const mockUseAppState = useAppState as jest.Mock<any>;

Expand Down Expand Up @@ -32,7 +32,7 @@ describe('the usePagination hook', () => {
act(() => {
result.current.setCurrentPage(4);
});
expect(result.current.paginatedParticipants).toEqual([8, 9, 10]);
expect(result.current.paginatedParticipants).toEqual([10]);
expect(result.current.currentPage).toBe(4);
});

Expand All @@ -45,12 +45,12 @@ describe('the usePagination hook', () => {
result.current.setCurrentPage(4);
});

expect(result.current.paginatedParticipants).toEqual([8, 9, 10]);
expect(result.current.paginatedParticipants).toEqual([10]);
expect(result.current.totalPages).toBe(4);

rerender({ participants: [1, 2, 3, 4, 5] });

expect(result.current.paginatedParticipants).toEqual([3, 4, 5]);
expect(result.current.paginatedParticipants).toEqual([4, 5]);
expect(result.current.totalPages).toBe(2);

rerender({ participants: [1, 2, 3] });
Expand Down Expand Up @@ -78,7 +78,7 @@ describe('the usePagination hook', () => {
result.current.setCurrentPage(4);
});

expect(result.current.paginatedParticipants).toEqual([8, 9, 10]);
expect(result.current.paginatedParticipants).toEqual([10]);
expect(result.current.totalPages).toBe(4);

mockUseAppState.mockImplementation(() => ({ maxGridParticipants: 2 }));
Expand All @@ -92,16 +92,16 @@ describe('the usePagination hook', () => {
const { result, rerender } = renderHook(() => usePagination([1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as any[]));

act(() => {
result.current.setCurrentPage(4);
result.current.setCurrentPage(3);
});

expect(result.current.paginatedParticipants).toEqual([8, 9, 10]);
expect(result.current.paginatedParticipants).toEqual([7, 8, 9]);
expect(result.current.totalPages).toBe(4);

mockUseAppState.mockImplementation(() => ({ maxGridParticipants: 4 }));
rerender();

expect(result.current.paginatedParticipants).toEqual([7, 8, 9, 10]);
expect(result.current.paginatedParticipants).toEqual([9, 10]);
expect(result.current.totalPages).toBe(3);
});
});
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { useState, useEffect } from 'react';
import { Participant } from 'twilio-video';
import { useAppState } from '../../state';
import { useAppState } from '../../../state';

export function usePagination(participants: Participant[]) {
const [currentPage, setCurrentPage] = useState(1); // Pages are 1 indexed
const { maxGridParticipants } = useAppState();

const totalPages = Math.ceil(participants.length / maxGridParticipants);
const isLastPage = currentPage === totalPages;
const isBeyondLastPage = currentPage > totalPages;

useEffect(() => {
Expand All @@ -16,16 +15,10 @@ export function usePagination(participants: Participant[]) {
}
}, [isBeyondLastPage, totalPages]);

let paginatedParticipants;

if (isLastPage) {
paginatedParticipants = participants.slice(-maxGridParticipants);
} else {
paginatedParticipants = participants.slice(
(currentPage - 1) * maxGridParticipants,
currentPage * maxGridParticipants
);
}
let paginatedParticipants = participants.slice(
(currentPage - 1) * maxGridParticipants,
currentPage * maxGridParticipants
);

return { paginatedParticipants, setCurrentPage, currentPage, totalPages };
}
Loading

0 comments on commit b913d5e

Please sign in to comment.