Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Countdown Timer Feature #185

Merged
merged 50 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
34e6d76
init
roman-drozd-it Mar 23, 2024
b7fee64
Adjust countdownTimerChip styles
roman-drozd-it Apr 12, 2024
cd92cfe
Clean component
roman-drozd-it Apr 15, 2024
980a6bc
Refactor component
roman-drozd-it Apr 15, 2024
f22addc
Make bigger refactor
roman-drozd-it Apr 16, 2024
688dc31
Comment code
roman-drozd-it Apr 16, 2024
f975ded
Clean code
roman-drozd-it Apr 16, 2024
9acb14a
Fix start when Enter key pressed
roman-drozd-it Apr 16, 2024
d68d0a3
Clean code
roman-drozd-it Apr 16, 2024
8263071
Clean code
roman-drozd-it Apr 16, 2024
f484dba
Clean code
roman-drozd-it Apr 16, 2024
8fdd479
Clean code
roman-drozd-it Apr 16, 2024
f9bd476
Fix delimiters
roman-drozd-it Apr 17, 2024
9eafd3f
Clean Code
roman-drozd-it Apr 18, 2024
f55cdb8
Standarize state
roman-drozd-it Apr 24, 2024
2648e9c
Standarize names
roman-drozd-it Apr 24, 2024
9c9531d
Show participant list (for testing)
roman-drozd-it Apr 24, 2024
2eae093
Fix counting stopped at 00:00:01
roman-drozd-it Apr 24, 2024
59bce3e
Fix opened TextField Date Picker at start (mobile)
roman-drozd-it Apr 24, 2024
ebe3113
Add visual indicator
roman-drozd-it Apr 25, 2024
5aa4626
Fix indicator for new joining user
roman-drozd-it Apr 25, 2024
6ea6476
Fix color format
roman-drozd-it Apr 25, 2024
abd9db2
Standarize names
roman-drozd-it Apr 25, 2024
fff6d4b
Open/close timer panel when clicking on Chip
roman-drozd-it Apr 26, 2024
ba680a8
Fromat code
roman-drozd-it Apr 26, 2024
0aed7a8
Simplify code
roman-drozd-it Apr 26, 2024
53e612d
Format code
roman-drozd-it Apr 26, 2024
30ed7be
Clean unused
roman-drozd-it Apr 26, 2024
a936e0d
Standarize names
roman-drozd-it Apr 26, 2024
70905a9
Clean unused
roman-drozd-it Apr 26, 2024
701b544
Fix name
roman-drozd-it Jun 27, 2024
cd6735d
Fix name
roman-drozd-it Jun 27, 2024
e3058ba
Fix name
roman-drozd-it Jun 27, 2024
96bc865
tmp
roman-drozd-it Jun 27, 2024
f23cb28
Manage mobile when format time is 00:00
roman-drozd-it Jul 3, 2024
696d74c
Add Permission
roman-drozd-it Jul 3, 2024
2b94d4d
Fix style
roman-drozd-it Jul 3, 2024
ad9bce1
Refactor code
roman-drozd-it Jul 4, 2024
96366ab
Add sound notification when time's up
roman-drozd-it Jul 4, 2024
ad83f52
Add text notification when time's up
roman-drozd-it Jul 4, 2024
9512409
Merge countdownTimerSlice with roomSlice
roman-drozd-it Jul 4, 2024
c8b6c0b
Revert showing participant list (for testing)
roman-drozd-it Jul 4, 2024
b94ed07
Remove unused Label
roman-drozd-it Jul 4, 2024
8bacd7a
Update translations
roman-drozd-it Jul 5, 2024
43ae64a
Restore original .eslintrc
roman-drozd-it Jul 5, 2024
40c5760
Temporary disable Eslint type error reporting
roman-drozd-it Jul 5, 2024
d17148a
Remove unused
roman-drozd-it Jul 5, 2024
cff2b34
Add missing comments
roman-drozd-it Sep 19, 2024
7f8f890
Standarize namespace
roman-drozd-it Sep 19, 2024
e7b8800
Merge branch 'main' into feat-room-countdown-timer
N7Remus Nov 18, 2024
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@reduxjs/toolkit": "^1.9.7",
"awaitqueue": "^3.0.2",
"bowser": "^2.11.0",
"moment": "^2.29.4",
"debug": "^4.3.7",
"dompurify": "^3.1.6",
"file-saver": "^2.0.5",
Expand Down
3 changes: 3 additions & 0 deletions public/config/config.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ var config = {
'raisedHand': {
'play': '/sounds/notify-hand.mp3'
},
'finishedCountdownTimer': {
'play': '/sounds/notify-countdowntimer.mp3'
},
'default': {
'debounce': 5000,
'play': '/sounds/notify.mp3'
Expand Down
Binary file added public/sounds/notify-countdowntimer.mp3
Binary file not shown.
152 changes: 152 additions & 0 deletions src/components/countdowntimer/CountdownTimer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import React, { useRef } from 'react';
import { IconButton, Grid, Switch, TextField, styled } from '@mui/material';
import { HighlightOff as HighlightOffIcon, Pause as PauseIcon, PlayArrow as PlayArrowIcon } from '@mui/icons-material';
import moment from 'moment';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import { setCountdownTimerInitialTime, startCountdownTimer, stopCountdownTimer, disableCountdownTimer, enableCountdownTimer } from '../../store/actions/countdownTimerActions';
import {
countdownTimerStartLabel, countdownTimerStopLabel,
countdownTimerEnableLabel, countdownTimerDisableLabel, countdownTimerSetLabel }
from '../translated/translatedComponents';
import { isMobileSelector } from '../../store/selectors';

const CountdownTimerDiv = styled('div')(({ theme }) => ({
display: 'flex',
marginRight: theme.spacing(1),
marginTop: theme.spacing(1),
flexDirection: 'column',
gap: theme.spacing(1),
}));

const CountdownTimer = () : JSX.Element => {
const isMobile = useAppSelector(isMobileSelector);
const dispatch = useAppDispatch();
const isEnabled = useAppSelector((state) => state.room.countdownTimer.isEnabled);
const isStarted = useAppSelector((state) => state.room.countdownTimer.isStarted);
const remainingTime = useAppSelector((state) => state.room.countdownTimer.remainingTime);

const inputRef = useRef<HTMLDivElement>(null);

const handleFocus = () => {

if (inputRef.current) {
inputRef.current.focus();
}

const timeout = setTimeout(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, 50);

return () => {
clearTimeout(timeout);
};
};

return (
<CountdownTimerDiv>
<Grid
sx={{ flexGrow: '1', justifyContent: 'space-between' }}
container wrap='nowrap'
alignItems='center' >

{/* set */}
<Grid item xs={8}>
<TextField fullWidth
aria-label={countdownTimerSetLabel()}
inputRef={inputRef}
autoFocus={!isMobile}
sx={{ flexGrow: '1' }}
variant='outlined'
label={(isMobile) ? 'timer (HH:mm)' : 'timer (HH:mm:ss)'}
disabled={!isEnabled || (isStarted && remainingTime !== '00:00:00')}
type='time'
value={remainingTime}
size='small'
InputLabelProps={{ shrink: true }}
inputProps={{ step: '1' }}
onChange={(e) => {
const time = (isMobile && moment(e.target.value, 'HH:mm', true).isValid())
? moment(`${e.target.value}:00`, 'HH:mm:ss').format('HH:mm:ss')
: moment(`${e.target.value}`, 'HH:mm:ss').format('HH:mm:ss');

dispatch(setCountdownTimerInitialTime(time));
}}
onKeyDown={(e) => {
if (remainingTime !== '00:00:00') {
if (e.key === 'Enter') {
dispatch(startCountdownTimer());
e.preventDefault();
}
}
}}
/>
</Grid>

{/* reset */}
<Grid item xs={1}>
<IconButton
aria-label={countdownTimerStartLabel()}
sx={{ flexGrow: '1' }}
color='error'
size='small'
disabled={ !isEnabled || (isStarted || remainingTime === '00:00:00') }
onClick={() => {
dispatch(setCountdownTimerInitialTime('00:00:00'));
}}
>
<HighlightOffIcon />
</IconButton>
</Grid>

{/* start/stop */}
<Grid item xs={1}>
<IconButton
aria-label={ !isStarted ?
countdownTimerStartLabel() :
countdownTimerStopLabel()
}
sx={{ flexGrow: '1' }}
color='error'
size='small'
disabled={!isEnabled || remainingTime === '00:00:00'}
onClick={() => {
if (!isStarted) {
dispatch(startCountdownTimer());
} else {
dispatch(stopCountdownTimer());
handleFocus();
}
}}
>
{!isStarted ? <PlayArrowIcon /> : <PauseIcon /> }
</IconButton>
</Grid>

{/* enable/disable */}
<Grid item xs={1}>
<Switch
aria-label={ !isStarted ?
countdownTimerDisableLabel() :
countdownTimerEnableLabel()
}
sx={{ flexGrow: '1' }}
checked={isEnabled}
disabled={isStarted}
onChange={() => {
dispatch(isEnabled ?
disableCountdownTimer() :
enableCountdownTimer()
);
}}
color='error'
size='small'
/>
</Grid>
</Grid>
</CountdownTimerDiv>
);
};

export default CountdownTimer;
53 changes: 53 additions & 0 deletions src/components/countdowntimer/CountdownTimerChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import { Chip } from '@mui/material';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import { uiActions } from '../../store/slices/uiSlice';
import AvTimerIcon from '@mui/icons-material/AvTimer';
import moment from 'moment';

const CountdownTimerChip = (): JSX.Element => {
const dispatch = useAppDispatch();
const isEnabled = useAppSelector((state) => state.room.countdownTimer.isEnabled);
const remainingTime = useAppSelector((state) => state.room.countdownTimer.remainingTime);
const initialTime = useAppSelector((state) => state.room.countdownTimer.initialTime);

const participantListOpen = useAppSelector((state) => state.ui.participantListOpen);

const openUsersTab = () => dispatch(uiActions.setUi({ participantListOpen: !participantListOpen }));

const secondsSet = moment.duration(initialTime).asSeconds();
const secondsLeft = moment.duration(remainingTime).asSeconds();
const percentage = parseFloat(((secondsLeft / secondsSet) * 100).toFixed(2));

let indicatorColor: string;
const backgroundColor: string = 'rgba(128, 128, 128, 0.5)'; // Declare the 'backgroundColor' variable here

switch (true) {
case percentage <= 100 && percentage >= 50: indicatorColor = '#2E7A27'; break;
case percentage < 50 && percentage >= 20: indicatorColor = '#FFA500'; break;
case percentage < 20: indicatorColor = '#FF0000'; break;
default: indicatorColor = backgroundColor;
}

return (
<>
{isEnabled && (
<Chip
sx={{
color: 'white',
backgroundColor: backgroundColor,
background: `linear-gradient(to right, ${indicatorColor} ${percentage}%, ${backgroundColor} ${percentage}%)`,
animation: `${percentage}% blink-animation 1s infinite`,
width: '86px',
}}
label={remainingTime}
size="small"
icon={<AvTimerIcon style={{ color: 'white' }} />}
onClick={() => openUsersTab()}
/>
)}
</>
);
};

export default CountdownTimerChip;
13 changes: 11 additions & 2 deletions src/components/participantlist/ParticipantList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { breakoutRoomsSelector, inParentRoomSelector, parentParticipantListSelec
import { permissions } from '../../utils/roles';
import {
breakoutRoomsLabel,
participantsLabel
participantsLabel,
countdownTimerTitleLabel
} from '../translated/translatedComponents';
import ListMe from './ListMe';
import ListModerator from './ListModerator';
import ListPeer from './ListPeer';
import BreakoutModerator from '../breakoutrooms/BreakoutModerator';
import ListBreakoutRoom from '../breakoutrooms/ListBreakoutRoom';
import CountdownTimer from '../countdowntimer/CountdownTimer';

const ParticipantListDiv = styled(Box)(({ theme }) => ({
width: '100%',
Expand All @@ -37,7 +39,14 @@ const ParticipantList = (): JSX.Element => {

return (
<ParticipantListDiv>
{ isModerator && <ListModerator /> }
{ isModerator && <>
<ListModerator />
<ListHeader>
{countdownTimerTitleLabel()}
</ListHeader>
<CountdownTimer />
</>
}
{ (breakoutsEnabled && (rooms.length > 0 || canCreateRooms)) &&
<>
<ListHeader>
Expand Down
6 changes: 5 additions & 1 deletion src/components/topbar/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import LeaveButton from '../textbuttons/LeaveButton';
import { formatDuration } from '../../utils/formatDuration';
import LogoutButton from '../controlbuttons/LogoutButton';
import RecordIcon from '../recordicon/RecordIcon';
import CountdownTimerChip from '../countdowntimer/CountdownTimerChip';

interface TopBarProps {
fullscreenEnabled: boolean;
Expand Down Expand Up @@ -120,9 +121,12 @@ const TopBar = ({ fullscreenEnabled, fullscreen, onFullscreen }: TopBarProps): R
{ canPromote && lobbyPeersLength > 0 && <LobbyButton type='iconbutton' /> }
{ loginEnabled && (loggedIn ? <LogoutButton type='iconbutton' /> : <LoginButton type='iconbutton' />) }
</TopBarDiv>
<TopBarDiv marginRight={2}>
<TopBarDiv marginRight={1}>
<StyledChip size='small' label={ formatDuration(meetingDuration) } />
</TopBarDiv>
<TopBarDiv marginRight={2}>
<CountdownTimerChip />
</TopBarDiv>
<LeaveButton />
</Toolbar>
</StyledAppBar>
Expand Down
34 changes: 33 additions & 1 deletion src/components/translated/translatedComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,39 @@ export const roomServerConnectionError = (message: string): string => intl.forma
defaultMessage: `Room-server: ${message}`
});

export const countdownTimerTitleLabel = (): string => intl.formatMessage({
id: 'label.countdownTimer.title',
defaultMessage: 'Countdown timer'
});

export const countdownTimerStartLabel = (): string => intl.formatMessage({
id: 'label.countdownTimer.start',
defaultMessage: 'Start'
});

export const countdownTimerStopLabel = (): string => intl.formatMessage({
id: 'label.countdownTimer.stop',
defaultMessage: 'Stop'
});

export const countdownTimerEnableLabel = (): string => intl.formatMessage({
id: 'label.countdownTimer.enable',
defaultMessage: 'Enable'
});

export const countdownTimerDisableLabel = (): string => intl.formatMessage({
id: 'label.countdownTimer.disable',
defaultMessage: 'Disable'
});

export const countdownTimerSetLabel = (): string => intl.formatMessage({
id: 'label.countdownTimer.set',
defaultMessage: 'Set'
});
export const countdownTimerFinishedLabel = (): string => intl.formatMessage({
id: 'label.countdownTimer.finished',
defaultMessage: 'Time is up!'
});
export const tenantSettingsLabel = (): string => intl.formatMessage({
id: 'label.managementTenantSettings',
defaultMessage: 'Tenant settings'
Expand Down Expand Up @@ -810,7 +843,6 @@ export const imprintLabel = (): string => intl.formatMessage({
id: 'label.imprint',
defaultMessage: 'Imprint'
});

export const privacyLabel = (): string => intl.formatMessage({
id: 'label.privacy',
defaultMessage: 'Privacy'
Expand Down
Loading
Loading