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

Feature: Timer on retro board #191

Merged
merged 56 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
6d0eeb6
feat: implemented custom timepicker component for the timer
Nov 23, 2023
91fa64a
feat: added timer, timer button and dialog
Nov 24, 2023
a61d4f6
feat: add disabled prop to timepicker
Nov 27, 2023
26b201c
Merge branch 'master' of github.com:MaibornWolff/retro into feature/t…
Nov 27, 2023
735cf26
fix: implement PR feedback+fixed sec. display prob
Nov 27, 2023
7e23c11
fix: custom duration, NaN check bug, PR feedback
Nov 29, 2023
962f10b
fix: improved number checking
Nov 29, 2023
8976173
fix: timer display in picker when running
Nov 29, 2023
ff3f7eb
fix: renamed method
Nov 29, 2023
df92243
feat: added custom confetti when timer ends
Nov 29, 2023
eece477
fix: rearranged particles packages
Nov 30, 2023
6c3ce2e
fix: simplified timer + new timers start correct
Nov 30, 2023
67bad31
feat: timer can be paused and resumed
Nov 30, 2023
0c0b6d8
feat: add color to timer pausing
Nov 30, 2023
869c886
fix: removed increment/decrement buttons
Nov 30, 2023
aee32f5
fix: button color change, timer edge case
Dec 1, 2023
45e82a8
feat: add timer events in the retroContext
Dec 5, 2023
412bf13
feat: timer over retrocontext
Dec 6, 2023
1607085
fix: time running end condition
Dec 6, 2023
cfb7990
fix: remove decrement / increment time
Dec 6, 2023
3d0b957
feat: add pause timer functionality
Dec 6, 2023
f3ca714
fix: show remaining time when paused
Nomandes Dec 7, 2023
22cfbd9
fix: add timer state values on import
Nomandes Dec 7, 2023
0f614bc
fix: renamed button & timer default value
Nomandes Dec 7, 2023
bd52ea3
fix: limit input field to max length
Nomandes Dec 7, 2023
cc2cb9c
feat: error state disables timer start
Nomandes Dec 7, 2023
e419b5d
feat: button wiggles on finish
Nomandes Dec 7, 2023
458f3ab
fix: remove particle modules
Nomandes Dec 7, 2023
06f6660
fix: timer button is visible while wiggling
Nomandes Dec 7, 2023
f0eca80
fix: cleanup
Nomandes Dec 8, 2023
cf2644d
fix: clean timepicker structure, removed ":"
Nomandes Dec 11, 2023
650dafa
fix: cleanup
Nomandes Dec 11, 2023
b5d589d
fix: rework WiggleActionButton
Nomandes Dec 11, 2023
1d91f4f
fix: renamed CreateTimerDialog
Nomandes Dec 11, 2023
b4654b1
fix: extract finish effect in hook
Nomandes Dec 11, 2023
203a755
fix: correct label at timer above 60 minutes
Nomandes Dec 11, 2023
897cb3b
feat: add timer increment buttons
Nomandes Dec 12, 2023
af95298
Merge branch 'master' into feature/timeboxing
Nomandes Dec 12, 2023
254ecf6
fix: prevent unnecessary effect timeouts
Nomandes Dec 12, 2023
29ecc37
fix: reshape button properties
Nomandes Dec 13, 2023
3c28352
fix: extract incrementTimerButton
Nomandes Dec 13, 2023
73fc2f2
fix: improve timelabel implementation
Nomandes Dec 13, 2023
7a9e479
feat: eslint checks line between functions
Nomandes Dec 13, 2023
af4a416
fix: internalized label
Nomandes Dec 13, 2023
a4e7b1a
fix: remove indirection in useValidatedTimeInput
Nomandes Dec 13, 2023
ba8e252
fix: added line between const and function to eslint
Nomandes Dec 13, 2023
4dfcbc8
fix: restructured components & cleanup
Nomandes Dec 13, 2023
ee20422
fix: rearranged closing, optimized naming + conditions
Nomandes Dec 13, 2023
1486760
feat: add eslint empty line between function & return
Nomandes Dec 13, 2023
97e2995
fix: set new duration at PAUSE_TIMER
Nomandes Dec 13, 2023
9b0bb12
fix: calculate milliseconds extracted to util
Nomandes Dec 13, 2023
bdf8384
fix: remove curly braces
Nomandes Dec 13, 2023
084e178
fix: add increment timer state
Nomandes Dec 14, 2023
6efb30a
refactor: rename increment timer action to change timer action
Dec 15, 2023
5f2b7a7
fix: show timer finish animation for participants
Dec 15, 2023
b53383f
refactor: remove unnecessary setting the timer again when pausing the…
Dec 15, 2023
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
6 changes: 6 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,11 @@ module.exports = {
"@typescript-eslint/no-unused-vars": "warn",
"@typescript-eslint/triple-slash-reference": "off",
"prettier/prettier": "warn",
"padding-line-between-statements": [
"warn",
{ blankLine: "always", prev: "block-like", next: "function" },
{ blankLine: "always", prev: "const", next: "function" },
{ blankLine: "always", prev: "function", next: "return" },
],
},
};
2 changes: 1 addition & 1 deletion packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"@mui/lab": "5.0.0-alpha.117",
"@mui/material": "^5.11.6",
"@shared/browserlogger": "*",
"@shared/socket": "*",
"@shared/configuration": "*",
"@shared/socket": "*",
"axios": "^1.6.0",
"lodash": "^4.17.21",
"nanoid": "^4.0.0",
Expand Down
11 changes: 5 additions & 6 deletions packages/frontend/src/common/components/buttons/ActionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
import { Button, ButtonProps, useTheme } from "@mui/material";
import React from "react";
import React, { MouseEventHandler } from "react";

interface ActionButtonProps extends ButtonProps {
onClick: () => void;
onClick: MouseEventHandler<HTMLButtonElement>;
label: string;
isDisabled?: boolean;
icon?: React.ReactNode;
}

export function ActionButton({ onClick, isDisabled, label, icon, ...props }: ActionButtonProps) {
export function ActionButton({ onClick, label, icon, ...props }: ActionButtonProps) {
const theme = useTheme();

return (
<Button
{...props}
variant="contained"
onClick={onClick}
disabled={isDisabled}
sx={{
m: 1,
borderRadius: theme.spacing(2),
boxShadow: "0px 5px 10px 0px rgba(0, 0, 0, 0.5)",
whiteSpace: "nowrap",
width: theme.spacing(22),
...props.sx,
}}
startIcon={icon ?? undefined}
startIcon={icon}
fullWidth
>
{label}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function PokerResultButton() {
<ActionButton
onClick={handleClick}
label="Show Results"
isDisabled={noUserVoted}
disabled={noUserVoted}
icon={<Visibility />}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function ResetVotesButton() {
<ActionButton
onClick={openDialog}
label="Reset Votes"
isDisabled={noUserVoted}
disabled={noUserVoted}
icon={<RestartAlt />}
/>
<ResetVotesDialog isOpen={isOpen} close={closeDialog} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export function CreatePokerSessionDialog() {
redirectToRoom(roomId);
handleClose();
}

return (
<Dialog
fullWidth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { CallToActionButton } from "../../../common/components/buttons/CallToAct
export function ResetVotesDialog({ isOpen, close }: DialogProps) {
const { handleResetUserStory } = usePokerContext();
const fullScreen = useFullscreen();

function handleClick() {
handleResetUserStory();
close();
Expand Down
2 changes: 2 additions & 0 deletions packages/frontend/src/retro/components/RetroActionButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { ToggleRetroBlurButton } from "./buttons/ToggleRetroBlurButton";
import { CreateColumnButton } from "./buttons/CreateColumnButton";
import { FlexBox } from "../../common/components/FlexBox";
import { ToggleRetroVotingButton } from "./buttons/ToggleRetroVotingButton";
import { ToggleTimerDialogButton } from "./buttons/ToggleTimerDialogButton";

export function RetroActionButtons() {
return (
<FlexBox flexDirection="row" p={1}>
<CreateColumnButton />
<ToggleRetroBlurButton />
<ToggleRetroVotingButton />
<ToggleTimerDialogButton />
</FlexBox>
);
}
59 changes: 59 additions & 0 deletions packages/frontend/src/retro/components/TimePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from "react";
import { FlexBox } from "../../common/components/FlexBox";
import { TextInput } from "../../common/components/TextInput";
import IncrementTimerButton from "./buttons/IncrementTimerButton";
interface TimePickerProps {
minutes: string;
seconds: string;
disabled: boolean;
isMinutesError: boolean;
isSecondsError: boolean;
onSecondsChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
onMinutesChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
onSubmit: () => void;
onTimerIncrement: (increment: number) => void;
}
export function TimePicker({
minutes,
seconds,
disabled,
isMinutesError,
isSecondsError,
onSecondsChange,
onMinutesChange,
onSubmit,
onTimerIncrement,
}: TimePickerProps) {
return (
<>
<FlexBox gap={2}>
<TextInput
value={minutes}
onChange={onMinutesChange}
onSubmit={onSubmit}
autoFocus
required
disabled={disabled}
type="tel"
error={isMinutesError}
label="Minutes"
/>
<TextInput
value={seconds}
onChange={onSecondsChange}
onSubmit={onSubmit}
required
type="tel"
disabled={disabled}
error={isSecondsError}
label="Seconds"
/>
</FlexBox>
<FlexBox>
<IncrementTimerButton onTimerIncrement={onTimerIncrement} minutesToIncrement={1} />
<IncrementTimerButton onTimerIncrement={onTimerIncrement} minutesToIncrement={3} />
<IncrementTimerButton onTimerIncrement={onTimerIncrement} minutesToIncrement={5} />
</FlexBox>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function CreateColumnButton() {
<ActionButton
onClick={openDialog}
label={"Add Column"}
isDisabled={!isModerator(user)}
disabled={!isModerator(user)}
icon={<Add />}
/>
<CreateColumnDialog isOpen={isOpen} close={closeDialog} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Publish } from "@mui/icons-material";
import { ListItemIcon, ListItemText, MenuItem } from "@mui/material";
import { useUserContext } from "../../../common/context/UserContext";
import { RetroSchemaV1 } from "../../types/retroSchema";
import { RetroState } from "../../types/retroTypes";
import { RetroState, TimerStatus } from "../../types/retroTypes";
import { useRetroContext } from "../../context/RetroContext";
import { isModerator } from "../../../common/utils/participantsUtils";

Expand All @@ -29,6 +29,8 @@ export function ImportRetroMenuItem() {
participants: {},
waitingList: {},
isVotingEnabled: false,
timerStatus: TimerStatus.STOPPED,
timerDuration: 0,
};
handleSetRetroState(retro);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";
import { ActionButton } from "../../../common/components/buttons/ActionButton";

interface IncrementTimerButtonProps {
onTimerIncrement: (amount: number) => void;
minutesToIncrement: number;
}
export default function IncrementTimerButton({
onTimerIncrement,
minutesToIncrement,
}: IncrementTimerButtonProps) {
return (
<ActionButton
label={`+${minutesToIncrement} Minutes`}
onClick={() => {
onTimerIncrement(minutesToIncrement);
}}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from "react";
import { Alarm, SnoozeOutlined } from "@mui/icons-material";
import { isModerator } from "../../../common/utils/participantsUtils";
import { TimerDialog } from "../dialogs/TimerDialog";
import { useRetroContext } from "../../context/RetroContext";
import { useUserContext } from "../../../common/context/UserContext";
import { useDialog } from "../../../common/hooks/useDialog";
import { useTimer } from "../../hooks/useTimer";

import { TimerStatus } from "../../types/retroTypes";
import { WiggleActionButton } from "./WiggleActionButton";
import useTimedEffect from "../../hooks/useTimedEffect";

export function ToggleTimerDialogButton() {
const { isOpen, closeDialog, openDialog } = useDialog();
const { retroState, handleStopTimer } = useRetroContext();
const { timerStatus } = retroState;
const { user } = useUserContext();

const { minutes, seconds, remainingTimeLabel } = useTimer({
onTimerFinish: handleTimerFinish,
});
const { isEffectActive, startEffect } = useTimedEffect({ effectLength: 3000 });

function handleOpenDialog() {
if (!isModerator(user)) return;
openDialog();
}

function handleTimerFinish() {
handleStopTimer();
startEffect();
}

if (!isModerator(user) && timerStatus === TimerStatus.STOPPED && !isEffectActive) return null;

return (
<>
<WiggleActionButton
onClick={handleOpenDialog}
label={timerStatus !== TimerStatus.STOPPED ? remainingTimeLabel : "Timer"}
icon={timerStatus === TimerStatus.PAUSED ? <SnoozeOutlined /> : <Alarm />}
color={
timerStatus === TimerStatus.PAUSED
? "info"
: timerStatus === TimerStatus.RUNNING || isEffectActive
? "error"
: undefined
}
isWiggling={isEffectActive}
/>
<TimerDialog
isOpen={isOpen}
close={closeDialog}
remainingMinutes={minutes}
remainingSeconds={seconds}
/>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { MouseEventHandler } from "react";
import { ButtonProps } from "@mui/material";
import { keyframes } from "@emotion/react";
import { ActionButton } from "../../../common/components/buttons/ActionButton";

interface WiggleActionButtonProps extends ButtonProps {
onClick: MouseEventHandler<HTMLButtonElement>;
label: string;
icon?: React.ReactNode;
isWiggling: boolean;
}

export function WiggleActionButton({
onClick,
label,
icon,
isWiggling,
...props
}: WiggleActionButtonProps) {
const wiggle = keyframes`
0%, 20%, 40%, 60%, 80%, 100% {
transform: rotate(0deg);
}
5%, 25%, 45%, 65%, 85% {
transform: rotate(5deg);
}
10%, 30%, 50%, 70%, 90% {
transform: rotate(-5deg);
}
15%, 35%, 55%, 75%, 95% {
transform: rotate(0deg);
}
`;
return (
<ActionButton
label={label}
onClick={onClick}
icon={icon}
sx={{ animation: isWiggling ? `${wiggle} 0.5s ease infinite` : undefined }}
{...props}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface SortColumnMenuItemProps {
export const SortColumnMenuItem = React.forwardRef(
({ column }: SortColumnMenuItemProps, ref: any) => {
const { handleSortCardsByVotesDescending } = useRetroContext();

function sortByVotesDescending() {
handleSortCardsByVotesDescending(column.index);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function QrCodeDialog({ isOpen, close }: DialogProps) {
async function onRendered() {
await QRCode.toCanvas(qrCanvas.current, window.location.href);
}

return (
<Dialog
fullScreen={fullScreen}
Expand Down
Loading