From e0184d519d28650df157cc28760b0f8a945e10c4 Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Sun, 20 Jun 2021 16:23:07 -0700 Subject: [PATCH 01/19] Fix Windows UI, disable problem looping, and prevent select when loading --- frontend/src/components/game/Editor.tsx | 2 +- .../src/components/game/PlayerGameView.tsx | 20 ++++++++++--------- frontend/src/components/problem/Selector.tsx | 7 ++++--- frontend/src/views/Lobby.tsx | 1 + 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/game/Editor.tsx b/frontend/src/components/game/Editor.tsx index 7a8ff31a..b13961c3 100644 --- a/frontend/src/components/game/Editor.tsx +++ b/frontend/src/components/game/Editor.tsx @@ -70,7 +70,7 @@ const LanguageSelect = styled.select` const monacoEditorOptions: EditorConstructionOptions = { automaticLayout: true, fixedOverflowWidgets: true, - fontFamily: 'Monaco', + fontFamily: 'Monaco, monospace', hideCursorInOverviewRuler: true, minimap: { enabled: false }, overviewRulerBorder: false, diff --git a/frontend/src/components/game/PlayerGameView.tsx b/frontend/src/components/game/PlayerGameView.tsx index d6850fe1..abf16f09 100644 --- a/frontend/src/components/game/PlayerGameView.tsx +++ b/frontend/src/components/game/PlayerGameView.tsx @@ -70,7 +70,7 @@ const LeaderboardContent = styled.div` text-align: center; margin: 0 auto; width: 75%; - overflow-x: scroll; + overflow-x: auto; white-space: nowrap; // Show shadows if there is scrollable content @@ -428,19 +428,21 @@ function PlayerGameView(props: PlayerGameViewProps) { }; const nextProblem = () => { - setCurrentProblemIndex((currentProblemIndex + 1) % problems?.length); - setCurrentSubmission(getSubmission((currentProblemIndex + 1) % problems?.length, submissions)); + const next = currentProblemIndex + 1; + + if (problems && next < problems.length) { + setCurrentProblemIndex(next); + setCurrentSubmission(getSubmission(next, submissions)); + } }; const previousProblem = () => { - let temp = currentProblemIndex - 1; + const prev = currentProblemIndex - 1; - if (temp < 0) { - temp += problems?.length; + if (prev >= 0) { + setCurrentProblemIndex(prev); + setCurrentSubmission(getSubmission(prev, submissions)); } - - setCurrentProblemIndex(temp); - setCurrentSubmission(getSubmission(temp, submissions)); }; const displayPlayerLeaderboard = useCallback(() => game?.players.map((player, index) => ( diff --git a/frontend/src/components/problem/Selector.tsx b/frontend/src/components/problem/Selector.tsx index 1e4defbb..3b19092d 100644 --- a/frontend/src/components/problem/Selector.tsx +++ b/frontend/src/components/problem/Selector.tsx @@ -13,6 +13,7 @@ import { problemMatchesFilterText } from '../../util/Utility'; type ProblemSelectorProps = { selectedProblems: SelectableProblem[], onSelect: (newlySelected: SelectableProblem) => void, + loading: boolean, }; type TagSelectorProps = { @@ -100,7 +101,7 @@ const ElementName = styled.p` `; export function ProblemSelector(props: ProblemSelectorProps) { - const { selectedProblems, onSelect } = props; + const { selectedProblems, onSelect, loading } = props; const [error, setError] = useState(''); const [verifiedProblems, setVerifiedProblems] = useState([]); @@ -151,9 +152,9 @@ export function ProblemSelector(props: ProblemSelectorProps) { return ( setShowProblems(!showProblems)} + onClick={() => !loading && setShowProblems(!showProblems)} onChange={setSearchStatus} - placeholder={allProblems.length ? 'Filter by name, difficulty, or tag (separate queries by comma)' : 'Loading...'} + placeholder={allProblems.length && !loading ? 'Filter by name, difficulty, or tag (separate queries by comma)' : 'Loading...'} /> diff --git a/frontend/src/views/Lobby.tsx b/frontend/src/views/Lobby.tsx index 059703d6..d45de61b 100644 --- a/frontend/src/views/Lobby.tsx +++ b/frontend/src/views/Lobby.tsx @@ -657,6 +657,7 @@ function LobbyPage() { Date: Sun, 20 Jun 2021 16:55:55 -0700 Subject: [PATCH 02/19] Refactor problem panel --- .../src/components/game/PlayerGameView.tsx | 79 ++----------------- frontend/src/components/game/ProblemPanel.tsx | 73 +++++++++++++++++ 2 files changed, 80 insertions(+), 72 deletions(-) create mode 100644 frontend/src/components/game/ProblemPanel.tsx diff --git a/frontend/src/components/game/PlayerGameView.tsx b/frontend/src/components/game/PlayerGameView.tsx index abf16f09..ea2a063b 100644 --- a/frontend/src/components/game/PlayerGameView.tsx +++ b/frontend/src/components/game/PlayerGameView.tsx @@ -6,9 +6,7 @@ import React, { } from 'react'; import styled from 'styled-components'; import SplitterLayout from 'react-splitter-layout'; -import MarkdownEditor from 'rich-markdown-editor'; import { useBeforeunload } from 'react-beforeunload'; -import copy from 'copy-to-clipboard'; import { Message, Subscription } from 'stompjs'; import Editor from './Editor'; import { DefaultCodeType, getDefaultCodeMap, Problem } from '../../api/Problem'; @@ -20,7 +18,7 @@ import { } from '../core/Container'; import ErrorMessage from '../core/Error'; import 'react-splitter-layout/lib/index.css'; -import { ProblemHeaderText, BottomFooterText, NoMarginDefaultText } from '../core/Text'; +import { NoMarginDefaultText } from '../core/Text'; import Console from './Console'; import Loading from '../core/Loading'; import { @@ -33,34 +31,14 @@ import { Player, } from '../../api/Game'; import LeaderboardCard from '../card/LeaderboardCard'; -import { getDifficultyDisplayButton, InheritedTextButton, SmallButton } from '../core/Button'; +import { SmallButton } from '../core/Button'; import { SpectatorBackIcon } from '../core/Icon'; import Language from '../../api/Language'; -import { CopyIndicator, BottomCopyIndicatorContainer, InlineCopyIcon } from '../special/CopyIndicator'; import { useAppSelector, useBestSubmission } from '../../util/Hook'; import { routes, send, subscribe } from '../../api/Socket'; import { User } from '../../api/User'; import { getScore, getSubmissionCount, getSubmissionTime } from '../../util/Utility'; - -const StyledMarkdownEditor = styled(MarkdownEditor)` - margin-top: 15px; - padding: 0; - - p { - font-family: ${({ theme }) => theme.font}; - } - - // The specific list of attributes to have dark text color. - .ProseMirror > p, blockquote, h1, h2, h3, ul, ol, table { - color: ${({ theme }) => theme.colors.text}; - } -`; - -const OverflowPanel = styled(Panel)` - overflow-y: auto; - height: 100%; - padding: 0 25px; -`; +import ProblemPanel from './ProblemPanel'; const NoPaddingPanel = styled(Panel)` padding: 0; @@ -158,7 +136,6 @@ function PlayerGameView(props: PlayerGameViewProps) { const { currentUser, game } = useAppSelector((state) => state); - const [copiedEmail, setCopiedEmail] = useState(false); const [submissions, setSubmissions] = useState([]); const [problems, setProblems] = useState([]); @@ -511,45 +488,9 @@ function PlayerGameView(props: PlayerGameViewProps) { secondaryMinSize={35} customClassName={!spectateGame ? 'game-splitter-container' : undefined} > - {/* Problem title/description panel */} - - - {!spectateGame - ? game?.problems[currentProblemIndex]?.name - : spectateGame?.problem.name} - - { - !spectateGame ? ( - getDifficultyDisplayButton(game?.problems[currentProblemIndex].difficulty!) - ) : ( - getDifficultyDisplayButton(spectateGame?.problem.difficulty!) - ) - } - ''} - readOnly - /> - - {'Notice an issue? Contact us at '} - { - copy('support@codejoust.co'); - setCopiedEmail(true); - }} - > - support@codejoust.co - content_copy - - - + {/* Code editor and console panels */} { @@ -582,7 +523,7 @@ function PlayerGameView(props: PlayerGameViewProps) { ) : ( - + Previous Next - - - setCopiedEmail(false)}> - Email copied!  ✕ - - ); } diff --git a/frontend/src/components/game/ProblemPanel.tsx b/frontend/src/components/game/ProblemPanel.tsx new file mode 100644 index 00000000..d084347a --- /dev/null +++ b/frontend/src/components/game/ProblemPanel.tsx @@ -0,0 +1,73 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import MarkdownEditor from 'rich-markdown-editor'; +import copy from 'copy-to-clipboard'; +import { BottomFooterText, ProblemHeaderText } from '../core/Text'; +import { getDifficultyDisplayButton, InheritedTextButton } from '../core/Button'; +import { BottomCopyIndicatorContainer, CopyIndicator, InlineCopyIcon } from '../special/CopyIndicator'; +import { Panel } from '../core/Container'; +import { Problem } from '../../api/Problem'; + +const StyledMarkdownEditor = styled(MarkdownEditor)` + margin-top: 15px; + padding: 0; + + p { + font-family: ${({ theme }) => theme.font}; + } + + // The specific list of attributes to have dark text color. + .ProseMirror > p, blockquote, h1, h2, h3, ul, ol, table { + color: ${({ theme }) => theme.colors.text}; + } +`; + +const OverflowPanel = styled(Panel)` + overflow-y: auto; + height: 100%; + padding: 0 25px; +`; + +type ProblemPanelProps = { + problem: Problem | undefined, +}; + +function ProblemPanel(props: ProblemPanelProps) { + const { problem } = props; + + const [copiedEmail, setCopiedEmail] = useState(false); + + return ( + + {problem?.name || 'Loading...'} + {problem ? getDifficultyDisplayButton(problem.difficulty) : null} + + ''} + readOnly + /> + + {'Notice an issue? Contact us at '} + { + copy('support@codejoust.co'); + setCopiedEmail(true); + }} + > + support@codejoust.co + content_copy + + + + + setCopiedEmail(false)}> + Email copied!  ✕ + + + + ); +} + +export default ProblemPanel; From d851869e95737672996ad229d76b68efd75fcd79 Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Sun, 20 Jun 2021 17:20:01 -0700 Subject: [PATCH 03/19] Move problem nav buttons and apply basic styling --- .../src/components/game/PlayerGameView.tsx | 6 +-- frontend/src/components/game/ProblemPanel.tsx | 39 ++++++++++++++++--- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/game/PlayerGameView.tsx b/frontend/src/components/game/PlayerGameView.tsx index ea2a063b..aaa701ce 100644 --- a/frontend/src/components/game/PlayerGameView.tsx +++ b/frontend/src/components/game/PlayerGameView.tsx @@ -31,7 +31,6 @@ import { Player, } from '../../api/Game'; import LeaderboardCard from '../card/LeaderboardCard'; -import { SmallButton } from '../core/Button'; import { SpectatorBackIcon } from '../core/Icon'; import Language from '../../api/Language'; import { useAppSelector, useBestSubmission } from '../../util/Hook'; @@ -490,6 +489,8 @@ function PlayerGameView(props: PlayerGameViewProps) { > 0 ? previousProblem : null} /> {/* Code editor and console panels */} @@ -539,9 +540,6 @@ function PlayerGameView(props: PlayerGameViewProps) { } - - Previous - Next ); } diff --git a/frontend/src/components/game/ProblemPanel.tsx b/frontend/src/components/game/ProblemPanel.tsx index d084347a..026df66f 100644 --- a/frontend/src/components/game/ProblemPanel.tsx +++ b/frontend/src/components/game/ProblemPanel.tsx @@ -3,9 +3,11 @@ import styled from 'styled-components'; import MarkdownEditor from 'rich-markdown-editor'; import copy from 'copy-to-clipboard'; import { BottomFooterText, ProblemHeaderText } from '../core/Text'; -import { getDifficultyDisplayButton, InheritedTextButton } from '../core/Button'; +import { DefaultButton, getDifficultyDisplayButton, InheritedTextButton } from '../core/Button'; import { BottomCopyIndicatorContainer, CopyIndicator, InlineCopyIcon } from '../special/CopyIndicator'; -import { Panel } from '../core/Container'; +import { + FlexHorizontalContainer, FlexLeft, FlexRight, Panel, +} from '../core/Container'; import { Problem } from '../../api/Problem'; const StyledMarkdownEditor = styled(MarkdownEditor)` @@ -28,19 +30,46 @@ const OverflowPanel = styled(Panel)` padding: 0 25px; `; +const ProblemNavContainer = styled(FlexRight)` + align-items: baseline; + padding: 15px 0; +`; + +const ProblemNavButton = styled(DefaultButton)` + font-size: ${({ theme }) => theme.fontSize.default}; + color: ${({ theme }) => theme.colors.gray}; + background-color: ${({ theme }) => theme.colors.white}; + border-radius: 5px; + width: 40px; + height: 40px; + margin: 5px; +`; + type ProblemPanelProps = { problem: Problem | undefined, + onNext: (() => void) | null, + onPrev: (() => void) | null, }; function ProblemPanel(props: ProblemPanelProps) { - const { problem } = props; + const { problem, onNext, onPrev } = props; const [copiedEmail, setCopiedEmail] = useState(false); return ( - {problem?.name || 'Loading...'} - {problem ? getDifficultyDisplayButton(problem.difficulty) : null} + + +
+ {problem?.name || 'Loading...'} + {problem ? getDifficultyDisplayButton(problem.difficulty) : null} +
+
+ + < + > + +
Date: Sun, 20 Jun 2021 18:42:43 -0700 Subject: [PATCH 04/19] Update styling and refactor existing icons --- frontend/src/components/core/Icon.tsx | 27 ++++++++------- frontend/src/components/game/ProblemPanel.tsx | 34 +++++++++++++++---- .../src/components/special/CopyIndicator.tsx | 11 ++++-- frontend/src/views/ContactUs.tsx | 2 +- frontend/src/views/Landing.tsx | 2 +- frontend/src/views/Lobby.tsx | 2 +- frontend/src/views/Results.tsx | 2 +- 7 files changed, 53 insertions(+), 27 deletions(-) diff --git a/frontend/src/components/core/Icon.tsx b/frontend/src/components/core/Icon.tsx index 9d6f7ed8..b70ac515 100644 --- a/frontend/src/components/core/Icon.tsx +++ b/frontend/src/components/core/Icon.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import styled from 'styled-components'; export const InlineIcon = styled.i.attrs(() => ({ @@ -38,24 +39,12 @@ export const InlineErrorIcon = styled(InlineIcon).attrs((props: ShowError) => ({ } `; -export const InlineShowIcon = styled.i.attrs(() => ({ - className: 'material-icons', -}))` - display: inline-block; - position: relative; - top: 0.1rem; - margin-left: 0.3rem; - border-radius: 1rem; - font-size: ${({ theme }) => theme.fontSize.medium}; - color: ${({ theme }) => theme.colors.font}; -`; - export const SpectatorBackIcon = styled.i.attrs(() => ({ className: 'material-icons', }))` position: absolute; top: 50%; - left: 0%; + left: 0; transform: translate(0%, -50%); text-align: center; margin: 0; @@ -70,3 +59,15 @@ export const SpectatorBackIcon = styled.i.attrs(() => ({ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.24); } `; + +export const PrevIcon = () => ( + + navigate_before + +); + +export const NextIcon = () => ( + + navigate_next + +); diff --git a/frontend/src/components/game/ProblemPanel.tsx b/frontend/src/components/game/ProblemPanel.tsx index 026df66f..9d2da3a5 100644 --- a/frontend/src/components/game/ProblemPanel.tsx +++ b/frontend/src/components/game/ProblemPanel.tsx @@ -9,6 +9,7 @@ import { FlexHorizontalContainer, FlexLeft, FlexRight, Panel, } from '../core/Container'; import { Problem } from '../../api/Problem'; +import { NextIcon, PrevIcon } from '../core/Icon'; const StyledMarkdownEditor = styled(MarkdownEditor)` margin-top: 15px; @@ -35,14 +36,29 @@ const ProblemNavContainer = styled(FlexRight)` padding: 15px 0; `; -const ProblemNavButton = styled(DefaultButton)` +type ProblemNavButtonProps = { + disabled: boolean, +}; + +const ProblemNavButton = styled(DefaultButton)` font-size: ${({ theme }) => theme.fontSize.default}; color: ${({ theme }) => theme.colors.gray}; - background-color: ${({ theme }) => theme.colors.white}; + background-color: ${({ theme, disabled }) => (disabled ? theme.colors.background : theme.colors.white)}; border-radius: 5px; - width: 40px; - height: 40px; + width: 35px; + height: 35px; margin: 5px; + + box-shadow: 0 1px 6px rgba(0, 0, 0, 0.16); + + &:hover { + box-shadow: ${({ disabled }) => (disabled ? '0 1px 6px rgba(0, 0, 0, 0.16)' : '0 1px 6px rgba(0, 0, 0, 0.20)')}; + cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')}; + } + + i { + line-height: 35px; + } `; type ProblemPanelProps = { @@ -66,8 +82,12 @@ function ProblemPanel(props: ProblemPanelProps) { - < - > + + + + + + @@ -86,7 +106,7 @@ function ProblemPanel(props: ProblemPanelProps) { }} > support@codejoust.co - content_copy + diff --git a/frontend/src/components/special/CopyIndicator.tsx b/frontend/src/components/special/CopyIndicator.tsx index f18f851f..1ef29c0a 100644 --- a/frontend/src/components/special/CopyIndicator.tsx +++ b/frontend/src/components/special/CopyIndicator.tsx @@ -1,6 +1,7 @@ import styled from 'styled-components'; import { DefaultButton } from '../core/Button'; import { ContactHeaderText } from '../core/Text'; +import React from 'react'; type CopyIndicator = { copied: boolean, @@ -50,9 +51,13 @@ export const InlineBackgroundCopyText = styled(ContactHeaderText)` cursor: pointer; `; -export const InlineCopyIcon = styled.i.attrs(() => ({ - className: 'material-icons', -}))` +const InlineCopyIconWrapper = styled.i` margin-left: 5px; font-size: inherit; `; + +export const InlineCopyIcon = () => ( + + content_copy + +); diff --git a/frontend/src/views/ContactUs.tsx b/frontend/src/views/ContactUs.tsx index 8ab76166..0f561e39 100644 --- a/frontend/src/views/ContactUs.tsx +++ b/frontend/src/views/ContactUs.tsx @@ -45,7 +45,7 @@ function ContactUsPage() { }} > support@codejoust.co - content_copy + . Say hello! diff --git a/frontend/src/views/Landing.tsx b/frontend/src/views/Landing.tsx index 910fce2a..4ef5e0fd 100644 --- a/frontend/src/views/Landing.tsx +++ b/frontend/src/views/Landing.tsx @@ -200,7 +200,7 @@ function LandingPage() { }} > hello@codejoust.co - content_copy + {' '} for one-on-one support within 24 hours. diff --git a/frontend/src/views/Lobby.tsx b/frontend/src/views/Lobby.tsx index d45de61b..858ed092 100644 --- a/frontend/src/views/Lobby.tsx +++ b/frontend/src/views/Lobby.tsx @@ -694,7 +694,7 @@ function LobbyPage() { > codejoust.co/play?room= {currentRoomId} - content_copy + {' '} or at diff --git a/frontend/src/views/Results.tsx b/frontend/src/views/Results.tsx index 5024fd4f..8dbaf3b9 100644 --- a/frontend/src/views/Results.tsx +++ b/frontend/src/views/Results.tsx @@ -232,7 +232,7 @@ function GameResultsPage() { > Invite - content_copy + ); From f7161b6e7775b07894886781f374839f16b926c7 Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Mon, 21 Jun 2021 20:29:43 -0700 Subject: [PATCH 05/19] Display index of problem in left panel --- .../src/components/game/PlayerGameView.tsx | 4 +- frontend/src/components/game/ProblemPanel.tsx | 41 +++++++++++++------ 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/game/PlayerGameView.tsx b/frontend/src/components/game/PlayerGameView.tsx index aaa701ce..1c165a8a 100644 --- a/frontend/src/components/game/PlayerGameView.tsx +++ b/frontend/src/components/game/PlayerGameView.tsx @@ -488,7 +488,9 @@ function PlayerGameView(props: PlayerGameViewProps) { customClassName={!spectateGame ? 'game-splitter-container' : undefined} > p.problemId === spectateGame.problem.problemId) || 0} onNext={currentProblemIndex < problems.length - 1 ? nextProblem : null} onPrev={currentProblemIndex > 0 ? previousProblem : null} /> diff --git a/frontend/src/components/game/ProblemPanel.tsx b/frontend/src/components/game/ProblemPanel.tsx index 9d2da3a5..b25804c1 100644 --- a/frontend/src/components/game/ProblemPanel.tsx +++ b/frontend/src/components/game/ProblemPanel.tsx @@ -2,10 +2,11 @@ import React, { useState } from 'react'; import styled from 'styled-components'; import MarkdownEditor from 'rich-markdown-editor'; import copy from 'copy-to-clipboard'; -import { BottomFooterText, ProblemHeaderText } from '../core/Text'; +import { BottomFooterText, ProblemHeaderText, SmallText } from '../core/Text'; import { DefaultButton, getDifficultyDisplayButton, InheritedTextButton } from '../core/Button'; import { BottomCopyIndicatorContainer, CopyIndicator, InlineCopyIcon } from '../special/CopyIndicator'; import { + CenteredContainer, FlexHorizontalContainer, FlexLeft, FlexRight, Panel, } from '../core/Container'; import { Problem } from '../../api/Problem'; @@ -36,6 +37,10 @@ const ProblemNavContainer = styled(FlexRight)` padding: 15px 0; `; +const ProblemCountText = styled(SmallText)` + color: gray; +`; + type ProblemNavButtonProps = { disabled: boolean, }; @@ -62,13 +67,16 @@ const ProblemNavButton = styled(DefaultButton)` `; type ProblemPanelProps = { - problem: Problem | undefined, + problems: Problem[], + index: number, onNext: (() => void) | null, onPrev: (() => void) | null, }; function ProblemPanel(props: ProblemPanelProps) { - const { problem, onNext, onPrev } = props; + const { + problems, index, onNext, onPrev, + } = props; const [copiedEmail, setCopiedEmail] = useState(false); @@ -77,23 +85,30 @@ function ProblemPanel(props: ProblemPanelProps) {
- {problem?.name || 'Loading...'} - {problem ? getDifficultyDisplayButton(problem.difficulty) : null} + {problems[index]?.name || 'Loading...'} + {problems[index] ? getDifficultyDisplayButton(problems[index].difficulty) : null}
- - - - - - + +
+ + + + + + +
+ + {`Problem ${index + 1} of ${problems.length}`} + +
''} readOnly /> From 62fbd626b87a1582f7e1b23cfeff7bfb0bdf968e Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Mon, 21 Jun 2021 20:37:29 -0700 Subject: [PATCH 06/19] Make problem slider only show for random selection --- frontend/src/views/Lobby.tsx | 72 +++++++++++++++--------------------- 1 file changed, 29 insertions(+), 43 deletions(-) diff --git a/frontend/src/views/Lobby.tsx b/frontend/src/views/Lobby.tsx index 858ed092..0356ca88 100644 --- a/frontend/src/views/Lobby.tsx +++ b/frontend/src/views/Lobby.tsx @@ -663,9 +663,7 @@ function LobbyPage() { problems={selectedProblems} onRemove={isHost(currentUser) ? removeProblem : null} /> - setShowProblemSelector(false)} - > + setShowProblemSelector(false)}> All Good! @@ -726,29 +724,17 @@ function LobbyPage() { Players - { - users - ? ` (${users.length})` - : null - } - + { users ? ` (${users.length})` : null } + refresh - setActionCardHelp(true)} - > + setActionCardHelp(true)}> help_outline - { - displayUsers(activeUsers, true) - } - { - displayUsers(inactiveUsers, false) - } + {displayUsers(activeUsers, true)} + {displayUsers(inactiveUsers, false)} { error ? : null } { loading ? : null } @@ -761,7 +747,7 @@ function LobbyPage() { Difficulty {isHost(currentUser) ? ( - Choose a difficulty for your randomly selected problem: + Choose a difficulty for your randomly selected problems: ) : null} @@ -783,6 +769,28 @@ function LobbyPage() { ); })} + Number of Problems + + {`${numProblems} problem${numProblems === 1 ? '' : 's'}`} + + + + + { + const newNumProblems = Number(e.target.value); + if (newNumProblems >= 1 && newNumProblems <= 10) { + setNumProblems(newNumProblems); + } + }} + onMouseUp={updateNumProblems} + /> + + ) : ( <> @@ -837,28 +845,6 @@ function LobbyPage() { /> - Number of Problems - - {`${numProblems} problem${numProblems === 1 ? '' : 's'}`} - - - - - { - const newNumProblems = Number(e.target.value); - if (newNumProblems >= 1 && newNumProblems <= 10) { - setNumProblems(newNumProblems); - } - }} - onMouseUp={updateNumProblems} - /> - - From e206afa45017b0365415189dd7b8bfce172d07df Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Mon, 21 Jun 2021 21:00:23 -0700 Subject: [PATCH 07/19] Update room service to dynamically update numProblems --- frontend/src/views/Lobby.tsx | 2 +- .../main/exception/ProblemError.java | 2 +- .../codejoust/main/service/RoomService.java | 17 ++++---- .../main/service/RoomServiceTests.java | 42 +++++++++++++++++++ 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/frontend/src/views/Lobby.tsx b/frontend/src/views/Lobby.tsx index 0356ca88..3c112f55 100644 --- a/frontend/src/views/Lobby.tsx +++ b/frontend/src/views/Lobby.tsx @@ -651,7 +651,7 @@ function LobbyPage() { Select problems for this game: Use the dropdown below to select the problems for your game. - Alternatively, skip this step to create a game with a random problem. + Alternatively, skip this step to create a game with random problems. { error ? : null } MAX_NUM_PROBLEMS) { + if (request.getNumProblems() <= 0 || request.getNumProblems() > MAX_NUM_PROBLEMS + || request.getProblems() != null && request.getProblems().size() > MAX_NUM_PROBLEMS) { throw new ApiException(ProblemError.INVALID_NUMBER_REQUEST); } room.setNumProblems(request.getNumProblems()); @@ -322,15 +323,11 @@ public RoomDto updateRoomSettings(String roomId, UpdateSettingsRequest request) return roomDto; } - private Room updateRoomSettingsSelectedProblems(List selectedProblems, Room room) { + private void updateRoomSettingsSelectedProblems(List selectedProblems, Room room) { // Set selected problems if not null boolean problemsHaveChanged = false; if (selectedProblems != null) { - if (selectedProblems.size() > room.getNumProblems()) { - throw new ApiException(RoomError.TOO_MANY_PROBLEMS); - } - problemsHaveChanged = checkSelectedProblemChanges(selectedProblems, room); } @@ -346,9 +343,13 @@ private Room updateRoomSettingsSelectedProblems(List selec newProblems.add(problem); } room.setProblems(newProblems); - } - return room; + if (newProblems.size() > 0) { + room.setNumProblems(newProblems.size()); + } else { + room.setNumProblems(1); + } + } } private boolean checkSelectedProblemChanges(List selectedProblems, Room room) { diff --git a/src/test/java/com/codejoust/main/service/RoomServiceTests.java b/src/test/java/com/codejoust/main/service/RoomServiceTests.java index f114df00..b8e0a97e 100644 --- a/src/test/java/com/codejoust/main/service/RoomServiceTests.java +++ b/src/test/java/com/codejoust/main/service/RoomServiceTests.java @@ -1,5 +1,6 @@ package com.codejoust.main.service; +import com.codejoust.main.model.problem.Problem; import com.codejoust.main.util.TestFields; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -20,6 +21,7 @@ import static org.mockito.Mockito.times; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -51,6 +53,9 @@ public class RoomServiceTests { @Mock private RoomRepository repository; + @Mock + private ProblemService problemService; + @Mock private SocketService socketService; @@ -606,6 +611,43 @@ public void updateRoomSettingsTooManySelectedProblems() { assertEquals(RoomError.TOO_MANY_PROBLEMS, exception.getError()); } + @Test + public void updateRoomSettingsChoosingProblemsModifiesNumProblems() { + /** + * 1. Update the room with two problems + * 2. Verify the numProblems field is now set to 2 + * 3. Get rid of all the problems + * 4. Verify the numProblems field is now set to 1 + */ + + Room room = new Room(); + room.setRoomId(TestFields.ROOM_ID); + User host = new User(); + host.setNickname(TestFields.NICKNAME); + room.setHost(host); + room.addUser(host); + + Mockito.doReturn(room).when(repository).findRoomByRoomId(eq(TestFields.ROOM_ID)); + Mockito.doReturn(new Problem()).when(problemService).getProblemEntity(Mockito.any()); + + UpdateSettingsRequest request = new UpdateSettingsRequest(); + request.setInitiator(UserMapper.toDto(host)); + request.setNumProblems(1); + + SelectableProblemDto problemDto = new SelectableProblemDto(); + problemDto.setProblemId(TestFields.PROBLEM_ID); + SelectableProblemDto problemDto2 = new SelectableProblemDto(); + problemDto.setProblemId(TestFields.PROBLEM_ID_2); + request.setProblems(Arrays.asList(problemDto, problemDto2)); + + RoomDto response = roomService.updateRoomSettings(TestFields.ROOM_ID, request); + assertEquals(2, response.getNumProblems()); + + request.setProblems(Collections.emptyList()); + response = roomService.updateRoomSettings(TestFields.ROOM_ID, request); + assertEquals(1, response.getNumProblems()); + } + @Test public void removeUserSuccessHostInitiator() { Room room = new Room(); From fcad04d6a1592510b4db25cf81812c5d7feeb405 Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Mon, 21 Jun 2021 21:44:06 -0700 Subject: [PATCH 08/19] Remove or update obselete tests --- .../main/service/GameManagementService.java | 22 ++--- .../com/codejoust/main/api/GameTests.java | 6 +- .../service/GameManagementServiceTests.java | 86 +------------------ .../main/service/RoomServiceTests.java | 26 ------ 4 files changed, 11 insertions(+), 129 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 510e1f07..6cb8382e 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -145,22 +145,14 @@ public void createAddGameFromRoom(Room room) { List problems = game.getProblems(); problems.addAll(room.getProblems()); - // Fill remaining problems with random ones by difficulty - int remaining = room.getNumProblems() - problems.size(); - - if (remaining < 0) { - throw new ApiException(RoomError.TOO_MANY_PROBLEMS); - } else if (remaining > 0) { - List otherProblems = problemService.getProblemsFromDifficulty(room.getDifficulty(), room.getNumProblems()); - for (Problem problem : otherProblems) { - if (!problems.contains(problem) && problems.size() < room.getNumProblems()) { - problems.add(problem); - } - } - } + // Otherwise, fetch random problems + if (problems.size() == 0) { + List randomProblems = problemService.getProblemsFromDifficulty(room.getDifficulty(), room.getNumProblems()); + problems.addAll(randomProblems); - if (problems.size() < room.getNumProblems()) { - throw new ApiException(ProblemError.NOT_ENOUGH_FOUND); + if (problems.size() < room.getNumProblems()) { + throw new ApiException(ProblemError.NOT_ENOUGH_FOUND); + } } setStartGameTimer(game, time); diff --git a/src/test/java/com/codejoust/main/api/GameTests.java b/src/test/java/com/codejoust/main/api/GameTests.java index 0a4f4ae6..a2c93ec3 100644 --- a/src/test/java/com/codejoust/main/api/GameTests.java +++ b/src/test/java/com/codejoust/main/api/GameTests.java @@ -127,11 +127,9 @@ public void startGameWithProblemIdGetsCorrectProblem() throws Exception { GameDto gameDto = MockHelper.getRequest(this.mockMvc, TestUrls.getGame(roomDto.getRoomId()), GameDto.class, HttpStatus.OK); - assertEquals(3, gameDto.getRoom().getNumProblems()); - assertEquals(3, gameDto.getProblems().size()); + assertEquals(1, gameDto.getRoom().getNumProblems()); + assertEquals(1, gameDto.getProblems().size()); assertEquals(problemDto.getProblemId(), gameDto.getProblems().get(0).getProblemId()); - assertNotEquals(gameDto.getProblems().get(0).getProblemId(), gameDto.getProblems().get(1).getProblemId()); - assertNotEquals(gameDto.getProblems().get(1).getProblemId(), gameDto.getProblems().get(2).getProblemId()); } @Test diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index 6c6e5705..daaf259a 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -159,57 +159,6 @@ public void startGameSuccess() { assertEquals(room.getDuration(), game.getGameTimer().getDuration()); } - @Test - public void startGameWithProblemAndRandomProblemSuccess() { - User host = new User(); - host.setNickname(TestFields.NICKNAME); - host.setUserId(TestFields.USER_ID); - - Room room = new Room(); - room.setRoomId(TestFields.ROOM_ID); - room.setHost(host); - room.setDifficulty(ProblemDifficulty.HARD); - room.setNumProblems(2); - - Problem problem = new Problem(); - problem.setProblemId(TestFields.PROBLEM_ID); - problem.setName(TestFields.PROBLEM_NAME); - problem.setDescription(TestFields.PROBLEM_DESCRIPTION); - problem.setDifficulty(ProblemDifficulty.EASY); - - Problem problem2 = new Problem(); - problem.setProblemId(TestFields.PROBLEM_ID_2); - problem.setName(TestFields.PROBLEM_NAME_2); - problem.setDescription(TestFields.PROBLEM_DESCRIPTION_2); - problem.setDifficulty(ProblemDifficulty.HARD); - - room.setProblems(Collections.singletonList(problem)); - - StartGameRequest request = new StartGameRequest(); - request.setInitiator(UserMapper.toDto(host)); - - Mockito.doReturn(room).when(repository).findRoomByRoomId(room.getRoomId()); - Mockito.doReturn(Arrays.asList(problem, problem2)) - .when(problemService).getProblemsFromDifficulty(ProblemDifficulty.HARD, 2); - - RoomDto response = gameService.startGame(TestFields.ROOM_ID, request); - - verify(socketService).sendSocketUpdate(eq(response)); - - assertEquals(TestFields.ROOM_ID, response.getRoomId()); - assertTrue(response.isActive()); - - Game game = gameService.getGameFromRoomId(TestFields.ROOM_ID); - assertNotNull(game); - - assertEquals(2, game.getRoom().getNumProblems()); - assertEquals(2, game.getProblems().size()); - assertEquals(problem.getProblemId(), game.getProblems().get(0).getProblemId()); - assertEquals(problem.getDifficulty(), game.getProblems().get(0).getDifficulty()); - assertEquals(problem2.getProblemId(), game.getProblems().get(1).getProblemId()); - assertEquals(problem2.getDescription(), game.getProblems().get(1).getDescription()); - } - @Test public void startGameWithOnlySelectedProblems() { User host = new User(); @@ -220,7 +169,7 @@ public void startGameWithOnlySelectedProblems() { room.setRoomId(TestFields.ROOM_ID); room.setHost(host); room.setDifficulty(ProblemDifficulty.HARD); - room.setNumProblems(2); + room.setNumProblems(5); Problem problem = new Problem(); problem.setProblemId(TestFields.PROBLEM_ID); @@ -240,47 +189,16 @@ public void startGameWithOnlySelectedProblems() { request.setInitiator(UserMapper.toDto(host)); Mockito.doReturn(room).when(repository).findRoomByRoomId(room.getRoomId()); + verify(problemService, never()).getProblemsFromDifficulty(Mockito.any(), Mockito.any()); gameService.startGame(TestFields.ROOM_ID, request); Game game = gameService.getGameFromRoomId(TestFields.ROOM_ID); - // Only 2 problems are selected (no duplicate problems are included) - assertEquals(2, game.getRoom().getNumProblems()); assertEquals(2, game.getProblems().size()); assertEquals(problem.getProblemId(), game.getProblems().get(0).getProblemId()); assertEquals(problem.getOutputType(), game.getProblems().get(0).getOutputType()); } - @Test - public void startGameNotEnoughProblems() { - User host = new User(); - host.setNickname(TestFields.NICKNAME); - host.setUserId(TestFields.USER_ID); - - Room room = new Room(); - room.setRoomId(TestFields.ROOM_ID); - room.setHost(host); - room.setDifficulty(ProblemDifficulty.EASY); - room.setNumProblems(2); - - Problem problem = new Problem(); - problem.setProblemId(TestFields.PROBLEM_ID); - problem.setName(TestFields.PROBLEM_NAME); - problem.setDescription(TestFields.PROBLEM_DESCRIPTION); - problem.setDifficulty(ProblemDifficulty.EASY); - room.setProblems(Collections.singletonList(problem)); - - StartGameRequest request = new StartGameRequest(); - request.setInitiator(UserMapper.toDto(host)); - - Mockito.doReturn(room).when(repository).findRoomByRoomId(room.getRoomId()); - Mockito.doReturn(Collections.singletonList(problem)) - .when(problemService).getProblemsFromDifficulty(ProblemDifficulty.EASY, 2); - - ApiException exception = assertThrows(ApiException.class, () -> gameService.startGame(TestFields.ROOM_ID, request)); - assertEquals(ProblemError.NOT_ENOUGH_FOUND, exception.getError()); - } - @Test public void startGameRoomNotFound() { UserDto user = new UserDto(); diff --git a/src/test/java/com/codejoust/main/service/RoomServiceTests.java b/src/test/java/com/codejoust/main/service/RoomServiceTests.java index b8e0a97e..7af70cf8 100644 --- a/src/test/java/com/codejoust/main/service/RoomServiceTests.java +++ b/src/test/java/com/codejoust/main/service/RoomServiceTests.java @@ -585,32 +585,6 @@ public void updateRoomSettingsExceedsMaxProblems() { assertEquals(ProblemError.INVALID_NUMBER_REQUEST, exception.getError()); } - @Test - public void updateRoomSettingsTooManySelectedProblems() { - Room room = new Room(); - room.setRoomId(TestFields.ROOM_ID); - User host = new User(); - host.setNickname(TestFields.NICKNAME); - room.setHost(host); - room.addUser(host); - - Mockito.doReturn(room).when(repository).findRoomByRoomId(eq(TestFields.ROOM_ID)); - - UpdateSettingsRequest request = new UpdateSettingsRequest(); - request.setInitiator(UserMapper.toDto(host)); - request.setNumProblems(1); - - SelectableProblemDto problemDto = new SelectableProblemDto(); - problemDto.setProblemId(TestFields.PROBLEM_ID); - SelectableProblemDto problemDto2 = new SelectableProblemDto(); - problemDto.setProblemId(TestFields.PROBLEM_ID_2); - request.setProblems(Arrays.asList(problemDto, problemDto2)); - - ApiException exception = assertThrows(ApiException.class, () -> - roomService.updateRoomSettings(TestFields.ROOM_ID, request)); - assertEquals(RoomError.TOO_MANY_PROBLEMS, exception.getError()); - } - @Test public void updateRoomSettingsChoosingProblemsModifiesNumProblems() { /** From 5c4cac3a6834bacee0a6cbb54d15a2d6295d5f1c Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Tue, 22 Jun 2021 19:40:38 -0700 Subject: [PATCH 09/19] Update help modal and fix bug with removing problems --- frontend/src/components/core/HelpModal.tsx | 2 +- frontend/src/views/Lobby.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/core/HelpModal.tsx b/frontend/src/components/core/HelpModal.tsx index d56ce3cc..b01fe265 100644 --- a/frontend/src/components/core/HelpModal.tsx +++ b/frontend/src/components/core/HelpModal.tsx @@ -103,7 +103,7 @@ export function LobbyHelpModal(props: HelpModalProps) { Difficulty : If no problems are selected, the host can choose a difficulty setting - and play the game with a randomly selected problem. + and play the game with randomly selected problems. <> diff --git a/frontend/src/views/Lobby.tsx b/frontend/src/views/Lobby.tsx index 3c112f55..05892669 100644 --- a/frontend/src/views/Lobby.tsx +++ b/frontend/src/views/Lobby.tsx @@ -797,7 +797,7 @@ function LobbyPage() { Problems )} From f7590324636e9ef2078419d846a516ae3db86e5b Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Tue, 22 Jun 2021 20:15:12 -0700 Subject: [PATCH 10/19] Implement new Copyable component to simplify code --- .../src/components/special/CopyIndicator.tsx | 37 ++++++++++++++++++- frontend/src/views/Landing.tsx | 24 ++---------- frontend/src/views/Lobby.tsx | 13 ++----- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/frontend/src/components/special/CopyIndicator.tsx b/frontend/src/components/special/CopyIndicator.tsx index 1ef29c0a..6d655ebc 100644 --- a/frontend/src/components/special/CopyIndicator.tsx +++ b/frontend/src/components/special/CopyIndicator.tsx @@ -1,7 +1,8 @@ +import React, { useState } from 'react'; import styled from 'styled-components'; -import { DefaultButton } from '../core/Button'; +import copy from 'copy-to-clipboard'; +import { DefaultButton, InheritedTextButton } from '../core/Button'; import { ContactHeaderText } from '../core/Text'; -import React from 'react'; type CopyIndicator = { copied: boolean, @@ -16,6 +17,7 @@ export const CopyIndicatorContainer = styled.div.attrs((props: CopyIndicator) => top: 20px; left: 50%; transition: transform 0.25s; + line-height: 0; z-index: 5; `; @@ -29,6 +31,7 @@ export const BottomCopyIndicatorContainer = styled.div.attrs((props: CopyIndicat bottom: 20px; left: 50%; transition: transform 0.25s; + line-height: 0; `; export const CopyIndicator = styled(DefaultButton)` @@ -36,6 +39,7 @@ export const CopyIndicator = styled(DefaultButton)` left: -50%; margin: 0 auto; padding: 0.25rem 1rem; + line-height: normal; color: ${({ theme }) => theme.colors.white}; background: ${({ theme }) => theme.colors.gradients.green}; `; @@ -61,3 +65,32 @@ export const InlineCopyIcon = () => ( content_copy ); + +type CopyableProps = { + text: string, +}; + +export function Copyable(props: CopyableProps) { + const { text } = props; + + const [copied, setCopied] = useState(false); + + return ( + <> + + setCopied(false)}> + Link copied!  ✕ + + + + { + copy(text); + setCopied(true); + }} + > + {text} + + + + ); +} diff --git a/frontend/src/views/Landing.tsx b/frontend/src/views/Landing.tsx index 4ef5e0fd..6b24acba 100644 --- a/frontend/src/views/Landing.tsx +++ b/frontend/src/views/Landing.tsx @@ -1,6 +1,5 @@ -import React, { useState } from 'react'; +import React from 'react'; import styled from 'styled-components'; -import copy from 'copy-to-clipboard'; import { PrimaryButtonLink, TextLink } from '../components/core/Link'; import { Image, ShadowImage } from '../components/core/Image'; import { @@ -9,8 +8,7 @@ import { import { ColumnContainer, RowContainer, Separator, TextLeftColumnContainer, } from '../components/core/Container'; -import { CopyIndicator, CopyIndicatorContainer, InlineCopyIcon } from '../components/special/CopyIndicator'; -import { InheritedTextButton } from '../components/core/Button'; +import { Copyable } from '../components/special/CopyIndicator'; import { FloatingCircles } from '../components/layout/CircleBackground'; const Content = styled.div` @@ -70,16 +68,8 @@ const CallToActionColumn = styled(ColumnContainer)` `; function LandingPage() { - const [copiedEmail, setCopiedEmail] = useState(false); - return ( - - setCopiedEmail(false)}> - Link copied!  ✕ - - - @@ -193,15 +183,7 @@ function LandingPage() { Create an account now or email us at {' '} - { - copy('hello@codejoust.co'); - setCopiedEmail(true); - }} - > - hello@codejoust.co - - + {' '} for one-on-one support within 24 hours. diff --git a/frontend/src/views/Lobby.tsx b/frontend/src/views/Lobby.tsx index 05892669..8f8b575b 100644 --- a/frontend/src/views/Lobby.tsx +++ b/frontend/src/views/Lobby.tsx @@ -717,7 +717,6 @@ function LobbyPage() { > Leave Room - @@ -725,18 +724,14 @@ function LobbyPage() { Players { users ? ` (${users.length})` : null } - - refresh - - setActionCardHelp(true)}> - help_outline - + refresh + setActionCardHelp(true)}>help_outline {displayUsers(activeUsers, true)} {displayUsers(inactiveUsers, false)} - { error ? : null } - { loading ? : null } + {error ? : null} + {loading ? : null} From d2930adcc59438ff728e32e6b155cdbb43b643f8 Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Tue, 22 Jun 2021 20:26:31 -0700 Subject: [PATCH 11/19] Update site to use new copyable component --- frontend/src/components/game/ProblemPanel.tsx | 28 +++----------- .../src/components/special/CopyIndicator.tsx | 17 +++++---- frontend/src/views/ContactUs.tsx | 21 +---------- frontend/src/views/Landing.tsx | 2 +- frontend/src/views/Lobby.tsx | 37 ++++++++----------- frontend/src/views/Results.tsx | 24 +++++------- 6 files changed, 43 insertions(+), 86 deletions(-) diff --git a/frontend/src/components/game/ProblemPanel.tsx b/frontend/src/components/game/ProblemPanel.tsx index b25804c1..c148059b 100644 --- a/frontend/src/components/game/ProblemPanel.tsx +++ b/frontend/src/components/game/ProblemPanel.tsx @@ -1,10 +1,9 @@ -import React, { useState } from 'react'; +import React from 'react'; import styled from 'styled-components'; import MarkdownEditor from 'rich-markdown-editor'; -import copy from 'copy-to-clipboard'; import { BottomFooterText, ProblemHeaderText, SmallText } from '../core/Text'; -import { DefaultButton, getDifficultyDisplayButton, InheritedTextButton } from '../core/Button'; -import { BottomCopyIndicatorContainer, CopyIndicator, InlineCopyIcon } from '../special/CopyIndicator'; +import { DefaultButton, getDifficultyDisplayButton } from '../core/Button'; +import { Copyable } from '../special/CopyIndicator'; import { CenteredContainer, FlexHorizontalContainer, FlexLeft, FlexRight, Panel, @@ -78,8 +77,6 @@ function ProblemPanel(props: ProblemPanelProps) { problems, index, onNext, onPrev, } = props; - const [copiedEmail, setCopiedEmail] = useState(false); - return ( @@ -113,23 +110,10 @@ function ProblemPanel(props: ProblemPanelProps) { readOnly /> - {'Notice an issue? Contact us at '} - { - copy('support@codejoust.co'); - setCopiedEmail(true); - }} - > - support@codejoust.co - - + Notice an issue? Contact us at + {' '} + - - - setCopiedEmail(false)}> - Email copied!  ✕ - - ); } diff --git a/frontend/src/components/special/CopyIndicator.tsx b/frontend/src/components/special/CopyIndicator.tsx index 6d655ebc..b35e71dd 100644 --- a/frontend/src/components/special/CopyIndicator.tsx +++ b/frontend/src/components/special/CopyIndicator.tsx @@ -8,7 +8,7 @@ type CopyIndicator = { copied: boolean, }; -export const CopyIndicatorContainer = styled.div.attrs((props: CopyIndicator) => ({ +const CopyIndicatorContainer = styled.div.attrs((props: CopyIndicator) => ({ style: { transform: (!props.copied) ? 'translateY(-60px)' : null, }, @@ -21,7 +21,7 @@ export const CopyIndicatorContainer = styled.div.attrs((props: CopyIndicator) => z-index: 5; `; -export const BottomCopyIndicatorContainer = styled.div.attrs((props: CopyIndicator) => ({ +const BottomCopyIndicatorContainer = styled.div.attrs((props: CopyIndicator) => ({ style: { transform: (!props.copied) ? 'translateY(60px)' : null, visibility: (props.copied) ? 'visible' : 'hidden', @@ -32,9 +32,10 @@ export const BottomCopyIndicatorContainer = styled.div.attrs((props: CopyIndicat left: 50%; transition: transform 0.25s; line-height: 0; + z-index: 5; `; -export const CopyIndicator = styled(DefaultButton)` +const CopyIndicator = styled(DefaultButton)` position: relative; left: -50%; margin: 0 auto; @@ -44,7 +45,7 @@ export const CopyIndicator = styled(DefaultButton)` background: ${({ theme }) => theme.colors.gradients.green}; `; -export const InlineBackgroundCopyText = styled(ContactHeaderText)` +const InlineBackgroundCopyText = styled(ContactHeaderText)` display: inline-block; margin: 0; padding: 0.25rem 0.5rem; @@ -68,20 +69,22 @@ export const InlineCopyIcon = () => ( type CopyableProps = { text: string, + top: boolean, }; export function Copyable(props: CopyableProps) { - const { text } = props; + const { text, top } = props; const [copied, setCopied] = useState(false); + const Container = top ? CopyIndicatorContainer : BottomCopyIndicatorContainer; return ( <> - + setCopied(false)}> Link copied!  ✕ - + { copy(text); diff --git a/frontend/src/views/ContactUs.tsx b/frontend/src/views/ContactUs.tsx index 0f561e39..76c72975 100644 --- a/frontend/src/views/ContactUs.tsx +++ b/frontend/src/views/ContactUs.tsx @@ -1,22 +1,13 @@ import React, { useState } from 'react'; -import copy from 'copy-to-clipboard'; import { DynamicWidthContainer } from '../components/core/Container'; import { InlineExternalLink } from '../components/core/Link'; import { ContactHeaderTitle, ContactHeaderText } from '../components/core/Text'; -import { CopyIndicator, CopyIndicatorContainer, InlineCopyIcon } from '../components/special/CopyIndicator'; import Subscribe from '../components/special/Subscribe'; -import { InheritedTextButton } from '../components/core/Button'; +import { Copyable } from '../components/special/CopyIndicator'; function ContactUsPage() { - const [copiedEmail, setCopiedEmail] = useState(false); - return ( <> - - setCopiedEmail(false)}> - Email copied!  ✕ - - Contact Us @@ -38,15 +29,7 @@ function ContactUsPage() { You can contact us at {' '} - { - copy('support@codejoust.co'); - setCopiedEmail(true); - }} - > - support@codejoust.co - - + . Say hello!
diff --git a/frontend/src/views/Landing.tsx b/frontend/src/views/Landing.tsx index 6b24acba..133ba277 100644 --- a/frontend/src/views/Landing.tsx +++ b/frontend/src/views/Landing.tsx @@ -183,7 +183,7 @@ function LandingPage() { Create an account now or email us at {' '} - + {' '} for one-on-one support within 24 hours. diff --git a/frontend/src/views/Lobby.tsx b/frontend/src/views/Lobby.tsx index 8f8b575b..191afb6b 100644 --- a/frontend/src/views/Lobby.tsx +++ b/frontend/src/views/Lobby.tsx @@ -4,7 +4,6 @@ import { unwrapResult } from '@reduxjs/toolkit'; import { Message, Subscription } from 'stompjs'; import { useBeforeunload } from 'react-beforeunload'; import styled from 'styled-components'; -import copy from 'copy-to-clipboard'; import ErrorMessage from '../components/core/Error'; import { NoMarginMediumText, @@ -39,12 +38,6 @@ import { setSpectator, } from '../api/Room'; import { errorHandler } from '../api/Error'; -import { - CopyIndicator, - CopyIndicatorContainer, - InlineCopyIcon, - InlineBackgroundCopyText, -} from '../components/special/CopyIndicator'; import IdContainer from '../components/special/IdContainer'; import { FlexBareContainer, LeftContainer } from '../components/core/Container'; import { Slider, SliderContainer } from '../components/core/RangeSlider'; @@ -675,25 +668,25 @@ function LobbyPage() { > Only the host can start the game and update settings - - setCopiedRoomLink(false)}> - Link copied!  ✕ - - + {/**/} + {/* setCopiedRoomLink(false)}>*/} + {/* Link copied!  ✕*/} + {/* */} + {/**/} Join with the link {' '} - { - copy(`https://codejoust.co/play?room=${currentRoomId}`); - setCopiedRoomLink(true); - }} - > - codejoust.co/play?room= - {currentRoomId} - - + {/* {*/} + {/* copy(`https://codejoust.co/play?room=${currentRoomId}`);*/} + {/* setCopiedRoomLink(true);*/} + {/* }}*/} + {/*>*/} + {/* codejoust.co/play?room=*/} + {/* {currentRoomId}*/} + {/* */} + {/**/} {' '} or at {' '} diff --git a/frontend/src/views/Results.tsx b/frontend/src/views/Results.tsx index 8dbaf3b9..9e32cdf1 100644 --- a/frontend/src/views/Results.tsx +++ b/frontend/src/views/Results.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; -import copy from 'copy-to-clipboard'; import { useBeforeunload } from 'react-beforeunload'; import { useLocation, useHistory } from 'react-router-dom'; import { Message } from 'stompjs'; @@ -18,11 +17,6 @@ import { import { User } from '../api/User'; import Podium from '../components/results/Podium'; import { HoverContainer, HoverElement, HoverTooltip } from '../components/core/HoverTooltip'; -import { - CopyIndicator, - CopyIndicatorContainer, - InlineCopyIcon, -} from '../components/special/CopyIndicator'; import ResultsTable from '../components/results/ResultsTable'; import Modal from '../components/core/Modal'; import FeedbackPopup from '../components/results/FeedbackPopup'; @@ -101,7 +95,7 @@ function GameResultsPage() { const [connected, setConnected] = useState(false); const [hoverVisible, setHoverVisible] = useState(false); - const [copiedRoomLink, setCopiedRoomLink] = useState(false); + // const [copiedRoomLink, setCopiedRoomLink] = useState(false); const [showFeedbackModal, setShowFeedbackModal] = useState(false); const [showFeedbackPrompt, setShowFeedbackPrompt] = useState(false); const [codeModal, setCodeModal] = useState(-1); @@ -226,24 +220,24 @@ function GameResultsPage() { const inviteContent = () => ( { - copy(`https://codejoust.co/play?room=${roomId}`); - setCopiedRoomLink(true); + // copy(`https://codejoust.co/play?room=${roomId}`); + // setCopiedRoomLink(true); }} > Invite - + {/**/} ); return ( - - setCopiedRoomLink(false)}> - Link copied!  ✕ - - + {/**/} + {/* setCopiedRoomLink(false)}>*/} + {/* Link copied!  ✕*/} + {/* */} + {/**/} Date: Tue, 22 Jun 2021 21:09:26 -0700 Subject: [PATCH 12/19] Implement copyable content for more customization --- frontend/src/components/core/Container.tsx | 4 ++ .../src/components/special/CopyIndicator.tsx | 39 +++++++++++-------- frontend/src/views/Lobby.tsx | 38 +++++++++--------- frontend/src/views/Results.tsx | 25 +++++------- 4 files changed, 56 insertions(+), 50 deletions(-) diff --git a/frontend/src/components/core/Container.tsx b/frontend/src/components/core/Container.tsx index 4da847ea..09154b5a 100644 --- a/frontend/src/components/core/Container.tsx +++ b/frontend/src/components/core/Container.tsx @@ -4,6 +4,10 @@ export const RelativeContainer = styled.div` position: relative; `; +export const InlineContainer = styled.div` + display: inline; +`; + export const FlexContainer = styled.div` display: flex; flex: auto; diff --git a/frontend/src/components/special/CopyIndicator.tsx b/frontend/src/components/special/CopyIndicator.tsx index b35e71dd..8d081c5e 100644 --- a/frontend/src/components/special/CopyIndicator.tsx +++ b/frontend/src/components/special/CopyIndicator.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import styled from 'styled-components'; import copy from 'copy-to-clipboard'; import { DefaultButton, InheritedTextButton } from '../core/Button'; -import { ContactHeaderText } from '../core/Text'; +import { InlineContainer } from '../core/Container'; type CopyIndicator = { copied: boolean, @@ -45,17 +45,6 @@ const CopyIndicator = styled(DefaultButton)` background: ${({ theme }) => theme.colors.gradients.green}; `; -const InlineBackgroundCopyText = styled(ContactHeaderText)` - display: inline-block; - margin: 0; - padding: 0.25rem 0.5rem; - font-size: ${({ theme }) => theme.fontSize.mediumLarge}; - background: ${({ theme }) => theme.colors.text}; - color: ${({ theme }) => theme.colors.white}; - border-radius: 0.5rem; - cursor: pointer; -`; - const InlineCopyIconWrapper = styled.i` margin-left: 5px; font-size: inherit; @@ -67,13 +56,19 @@ export const InlineCopyIcon = () => ( ); +type CopyableContentProps = { + children: React.ReactNode, + text: string, + top: boolean, +}; + type CopyableProps = { text: string, top: boolean, }; -export function Copyable(props: CopyableProps) { - const { text, top } = props; +export function CopyableContent(props: CopyableContentProps) { + const { children, text, top } = props; const [copied, setCopied] = useState(false); const Container = top ? CopyIndicatorContainer : BottomCopyIndicatorContainer; @@ -86,14 +81,26 @@ export function Copyable(props: CopyableProps) { - { + { copy(text); setCopied(true); }} > + {children} + + + ); +} + +export function Copyable(props: CopyableProps) { + const { text, top } = props; + + return ( + + {text} - + ); } diff --git a/frontend/src/views/Lobby.tsx b/frontend/src/views/Lobby.tsx index 191afb6b..d73f9916 100644 --- a/frontend/src/views/Lobby.tsx +++ b/frontend/src/views/Lobby.tsx @@ -12,6 +12,7 @@ import { NoMarginSubtitleText, LowMarginText, Text, + ContactHeaderText, } from '../components/core/Text'; import { connect, routes, subscribe, disconnect, @@ -51,6 +52,7 @@ import { setCurrentUser } from '../redux/User'; import { LobbyHelpModal } from '../components/core/HelpModal'; import Modal from '../components/core/Modal'; import { setGame } from '../redux/Game'; +import { CopyableContent, InlineCopyIcon } from '../components/special/CopyIndicator'; type LobbyPageLocation = { user: User, @@ -143,6 +145,18 @@ const ExitModalButton = styled(PrimaryButton)` margin: 20px 0; `; +const CopyableRoomLink = styled(ContactHeaderText)` + display: inline-block; + margin: 0; + padding: 0.25rem 0.5rem; + font-size: ${({ theme }) => theme.fontSize.mediumLarge}; + background: ${({ theme }) => theme.colors.text}; + color: ${({ theme }) => theme.colors.white}; + border-radius: 0.5rem; + cursor: pointer; + text-decoration: none; +`; + function LobbyPage() { // Get history object to be able to move between different pages const history = useHistory(); @@ -179,9 +193,6 @@ function LobbyPage() { // Variable to hold the socket subscription, or null if not connected const [subscription, setSubscription] = useState(null); - // Variable to hold whether the room link was copied. - const [copiedRoomLink, setCopiedRoomLink] = useState(false); - // Variable to hold whether the modal explaining the user cards is active. const [actionCardHelp, setActionCardHelp] = useState(false); @@ -668,25 +679,16 @@ function LobbyPage() { > Only the host can start the game and update settings - {/**/} - {/* setCopiedRoomLink(false)}>*/} - {/* Link copied!  ✕*/} - {/* */} - {/**/} Join with the link {' '} - {/* {*/} - {/* copy(`https://codejoust.co/play?room=${currentRoomId}`);*/} - {/* setCopiedRoomLink(true);*/} - {/* }}*/} - {/*>*/} - {/* codejoust.co/play?room=*/} - {/* {currentRoomId}*/} - {/* */} - {/**/} + + + {`codejoust.co/play?room=${currentRoomId}`} + + + {' '} or at {' '} diff --git a/frontend/src/views/Results.tsx b/frontend/src/views/Results.tsx index 9e32cdf1..b7b03fbb 100644 --- a/frontend/src/views/Results.tsx +++ b/frontend/src/views/Results.tsx @@ -25,6 +25,7 @@ import { fetchGame, setGame } from '../redux/Game'; import { setCurrentUser } from '../redux/User'; import { setRoom } from '../redux/Room'; import PreviewCodeContent from '../components/results/PreviewCodeContent'; +import { CopyableContent, InlineCopyIcon } from '../components/special/CopyIndicator'; const Content = styled.div` padding: 0; @@ -218,26 +219,18 @@ function GameResultsPage() { // Content to display for inviting players (if not enough players on the podium) const inviteContent = () => ( - { - // copy(`https://codejoust.co/play?room=${roomId}`); - // setCopiedRoomLink(true); - }} - > - - Invite - {/**/} - - + + + + Invite + + + + ); return ( - {/**/} - {/* setCopiedRoomLink(false)}>*/} - {/* Link copied!  ✕*/} - {/* */} - {/**/} Date: Tue, 22 Jun 2021 22:03:43 -0700 Subject: [PATCH 13/19] Implement timeout on copy indicator --- .../src/components/special/CopyIndicator.tsx | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/special/CopyIndicator.tsx b/frontend/src/components/special/CopyIndicator.tsx index 8d081c5e..a313c293 100644 --- a/frontend/src/components/special/CopyIndicator.tsx +++ b/frontend/src/components/special/CopyIndicator.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useRef } from 'react'; import styled from 'styled-components'; import copy from 'copy-to-clipboard'; import { DefaultButton, InheritedTextButton } from '../core/Button'; @@ -71,21 +71,37 @@ export function CopyableContent(props: CopyableContentProps) { const { children, text, top } = props; const [copied, setCopied] = useState(false); + const timeoutRef = useRef | null>(null); const Container = top ? CopyIndicatorContainer : BottomCopyIndicatorContainer; + const onCopy = () => { + copy(text); + setCopied(true); + + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => setCopied(false), 2000); + }; + + const closeIndicator = () => { + setCopied(false); + + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }; + return ( <> - setCopied(false)}> - Link copied!  ✕ + + Copied!  ✕ - { - copy(text); - setCopied(true); - }} - > + {children} From de85269f27b46abce21d3005b52bfbcae0b49c55 Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Tue, 29 Jun 2021 20:12:36 -0700 Subject: [PATCH 14/19] Hold problems and submit all at once --- frontend/src/views/Lobby.tsx | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/frontend/src/views/Lobby.tsx b/frontend/src/views/Lobby.tsx index 358fcfef..29cb6a4c 100644 --- a/frontend/src/views/Lobby.tsx +++ b/frontend/src/views/Lobby.tsx @@ -172,6 +172,7 @@ function LobbyPage() { const [difficulty, setDifficulty] = useState(null); const [duration, setDuration] = useState(15); const [selectedProblems, setSelectedProblems] = useState([]); + const [tempSelectedProblems, setTempSelectedProblems] = useState([]); const [size, setSize] = useState(10); const [numProblems, setNumProblems] = useState(1); const [hoverVisible, setHoverVisible] = useState(false); @@ -421,8 +422,11 @@ function LobbyPage() { setError(''); setLoading(true); + console.log(newProblems); + const prevProblems = selectedProblems; setSelectedProblems(newProblems); + setTempSelectedProblems([]); const settings = { initiator: currentUser!, @@ -433,15 +437,26 @@ function LobbyPage() { .catch((err) => { setError(err.message); setSelectedProblems(prevProblems); + console.log('err'); }) .finally(() => { setLoading(false); + console.log(selectedProblems); + console.log(tempSelectedProblems); }); }; + // Add a problem to be selected - these will be submitted as a batch. const addProblem = (newProblem: SelectableProblem) => { - const newProblems = [...selectedProblems, newProblem]; - updateSelectedProblems(newProblems); + const problemsToUse = tempSelectedProblems.length ? tempSelectedProblems : selectedProblems; + const newProblems = [...problemsToUse, newProblem]; + setTempSelectedProblems(newProblems); + }; + + // Submit the batch of selected problems and close the modal. + const submitTempProblems = () => { + updateSelectedProblems(tempSelectedProblems); + setShowProblemSelector(false); }; const removeProblem = (index: number) => { @@ -671,15 +686,15 @@ function LobbyPage() { { error ? : null } - setShowProblemSelector(false)}> + All Good! @@ -798,7 +813,7 @@ function LobbyPage() { <> Problems From 697f7c49c7398c9ee25a3045df681a97849e2373 Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Tue, 29 Jun 2021 20:54:09 -0700 Subject: [PATCH 15/19] Handle edge cases with batch problem selection --- frontend/src/views/Lobby.tsx | 38 ++++++++++--------- .../codejoust/main/service/RoomService.java | 4 ++ 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/frontend/src/views/Lobby.tsx b/frontend/src/views/Lobby.tsx index 29cb6a4c..9f495089 100644 --- a/frontend/src/views/Lobby.tsx +++ b/frontend/src/views/Lobby.tsx @@ -173,6 +173,7 @@ function LobbyPage() { const [duration, setDuration] = useState(15); const [selectedProblems, setSelectedProblems] = useState([]); const [tempSelectedProblems, setTempSelectedProblems] = useState([]); + const [modifiedProblems, setModifiedProblems] = useState(false); const [size, setSize] = useState(10); const [numProblems, setNumProblems] = useState(1); const [hoverVisible, setHoverVisible] = useState(false); @@ -222,7 +223,10 @@ function LobbyPage() { setActive(newRoom.active); setDifficulty(newRoom.difficulty); setDuration(newRoom.duration / 60); - setSelectedProblems(newRoom.problems); + // If problems were modified, do not set the problems + if (!modifiedProblems) { + setSelectedProblems(newRoom.problems); + } setSize(newRoom.size); setNumProblems(newRoom.numProblems); @@ -422,11 +426,8 @@ function LobbyPage() { setError(''); setLoading(true); - console.log(newProblems); - const prevProblems = selectedProblems; setSelectedProblems(newProblems); - setTempSelectedProblems([]); const settings = { initiator: currentUser!, @@ -437,20 +438,20 @@ function LobbyPage() { .catch((err) => { setError(err.message); setSelectedProblems(prevProblems); - console.log('err'); }) .finally(() => { setLoading(false); - console.log(selectedProblems); - console.log(tempSelectedProblems); + setTempSelectedProblems([]); + setModifiedProblems(false); }); }; // Add a problem to be selected - these will be submitted as a batch. const addProblem = (newProblem: SelectableProblem) => { - const problemsToUse = tempSelectedProblems.length ? tempSelectedProblems : selectedProblems; + const problemsToUse = modifiedProblems ? tempSelectedProblems : selectedProblems; const newProblems = [...problemsToUse, newProblem]; setTempSelectedProblems(newProblems); + setModifiedProblems(true); }; // Submit the batch of selected problems and close the modal. @@ -459,9 +460,12 @@ function LobbyPage() { setShowProblemSelector(false); }; - const removeProblem = (index: number) => { - const newProblems = selectedProblems.filter((_, i) => i !== index); - updateSelectedProblems(newProblems); + const removeTempProblem = (index: number) => { + const problemsToUse = modifiedProblems ? tempSelectedProblems : selectedProblems; + const newProblems = problemsToUse.filter((_, i) => i !== index); + + setTempSelectedProblems(newProblems); + setModifiedProblems(true); }; const onSizeSliderChange = (e: React.ChangeEvent) => { @@ -686,15 +690,15 @@ function LobbyPage() { { error ? : null } - + All Good! @@ -813,8 +817,8 @@ function LobbyPage() { <> Problems )} diff --git a/src/main/java/com/codejoust/main/service/RoomService.java b/src/main/java/com/codejoust/main/service/RoomService.java index bc5695b9..290d8a5e 100644 --- a/src/main/java/com/codejoust/main/service/RoomService.java +++ b/src/main/java/com/codejoust/main/service/RoomService.java @@ -351,6 +351,10 @@ private void updateRoomSettingsSelectedProblems(List selec boolean problemsHaveChanged = false; if (selectedProblems != null) { + if (selectedProblems.size() > MAX_NUM_PROBLEMS) { + throw new ApiException(ProblemError.INVALID_NUMBER_REQUEST); + } + problemsHaveChanged = checkSelectedProblemChanges(selectedProblems, room); } From 85ee691c0a3abed400c232f1735b3d3cde2ff163 Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Tue, 29 Jun 2021 21:38:52 -0700 Subject: [PATCH 16/19] Update button styling and remove comment --- frontend/src/components/game/ProblemPanel.tsx | 4 ++-- frontend/src/views/Results.tsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/game/ProblemPanel.tsx b/frontend/src/components/game/ProblemPanel.tsx index c148059b..b62b9f41 100644 --- a/frontend/src/components/game/ProblemPanel.tsx +++ b/frontend/src/components/game/ProblemPanel.tsx @@ -46,8 +46,8 @@ type ProblemNavButtonProps = { const ProblemNavButton = styled(DefaultButton)` font-size: ${({ theme }) => theme.fontSize.default}; - color: ${({ theme }) => theme.colors.gray}; - background-color: ${({ theme, disabled }) => (disabled ? theme.colors.background : theme.colors.white)}; + color: ${({ theme, disabled }) => (disabled ? theme.colors.lightgray : theme.colors.gray)}; + background-color: ${({ theme }) => theme.colors.white}; border-radius: 5px; width: 35px; height: 35px; diff --git a/frontend/src/views/Results.tsx b/frontend/src/views/Results.tsx index b7b03fbb..6318306b 100644 --- a/frontend/src/views/Results.tsx +++ b/frontend/src/views/Results.tsx @@ -96,7 +96,6 @@ function GameResultsPage() { const [connected, setConnected] = useState(false); const [hoverVisible, setHoverVisible] = useState(false); - // const [copiedRoomLink, setCopiedRoomLink] = useState(false); const [showFeedbackModal, setShowFeedbackModal] = useState(false); const [showFeedbackPrompt, setShowFeedbackPrompt] = useState(false); const [codeModal, setCodeModal] = useState(-1); From ebe157c6467bf07ea024bb1a9435c4ededcc1b9f Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Tue, 29 Jun 2021 23:32:41 -0700 Subject: [PATCH 17/19] Implement line clamp functionality --- frontend/src/components/game/ProblemPanel.tsx | 35 +++++++++++++------ .../src/components/special/CopyIndicator.tsx | 1 - 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/game/ProblemPanel.tsx b/frontend/src/components/game/ProblemPanel.tsx index b62b9f41..45da1749 100644 --- a/frontend/src/components/game/ProblemPanel.tsx +++ b/frontend/src/components/game/ProblemPanel.tsx @@ -5,8 +5,7 @@ import { BottomFooterText, ProblemHeaderText, SmallText } from '../core/Text'; import { DefaultButton, getDifficultyDisplayButton } from '../core/Button'; import { Copyable } from '../special/CopyIndicator'; import { - CenteredContainer, - FlexHorizontalContainer, FlexLeft, FlexRight, Panel, + CenteredContainer, FlexLeft, FlexRight, Panel, } from '../core/Container'; import { Problem } from '../../api/Problem'; import { NextIcon, PrevIcon } from '../core/Icon'; @@ -31,7 +30,23 @@ const OverflowPanel = styled(Panel)` padding: 0 25px; `; -const ProblemNavContainer = styled(FlexRight)` +const HeaderContainer = styled.div` + display: flex; + flex: auto; + justify-content: space-between; +`; + +const TitleContainer = styled.div` + display: -webkit-box; + -webkit-line-clamp: 1; + overflow: hidden; + -webkit-box-orient: vertical; + word-break: break-all; +`; + +const ProblemNavContainer = styled.div` + width: 100px; + min-width: 100px; align-items: baseline; padding: 15px 0; `; @@ -79,13 +94,13 @@ function ProblemPanel(props: ProblemPanelProps) { return ( - - -
+ +
+ {problems[index]?.name || 'Loading...'} - {problems[index] ? getDifficultyDisplayButton(problems[index].difficulty) : null} -
- + + {problems[index] ? getDifficultyDisplayButton(problems[index].difficulty) : null} +
@@ -101,7 +116,7 @@ function ProblemPanel(props: ProblemPanelProps) { - + ({ const BottomCopyIndicatorContainer = styled.div.attrs((props: CopyIndicator) => ({ style: { transform: (!props.copied) ? 'translateY(60px)' : null, - visibility: (props.copied) ? 'visible' : 'hidden', }, }))` position: fixed; From 2d57fbabf55b04a575b8df508dd03a6160adb3db Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Thu, 1 Jul 2021 21:51:04 -0700 Subject: [PATCH 18/19] Fix dependency warnings --- frontend/src/components/game/ProblemPanel.tsx | 4 +--- frontend/src/views/ContactUs.tsx | 2 +- frontend/src/views/Lobby.tsx | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/game/ProblemPanel.tsx b/frontend/src/components/game/ProblemPanel.tsx index 45da1749..fa52cd63 100644 --- a/frontend/src/components/game/ProblemPanel.tsx +++ b/frontend/src/components/game/ProblemPanel.tsx @@ -4,9 +4,7 @@ import MarkdownEditor from 'rich-markdown-editor'; import { BottomFooterText, ProblemHeaderText, SmallText } from '../core/Text'; import { DefaultButton, getDifficultyDisplayButton } from '../core/Button'; import { Copyable } from '../special/CopyIndicator'; -import { - CenteredContainer, FlexLeft, FlexRight, Panel, -} from '../core/Container'; +import { CenteredContainer, Panel } from '../core/Container'; import { Problem } from '../../api/Problem'; import { NextIcon, PrevIcon } from '../core/Icon'; diff --git a/frontend/src/views/ContactUs.tsx b/frontend/src/views/ContactUs.tsx index 76c72975..ef6b2bf5 100644 --- a/frontend/src/views/ContactUs.tsx +++ b/frontend/src/views/ContactUs.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { DynamicWidthContainer } from '../components/core/Container'; import { InlineExternalLink } from '../components/core/Link'; import { ContactHeaderTitle, ContactHeaderText } from '../components/core/Text'; diff --git a/frontend/src/views/Lobby.tsx b/frontend/src/views/Lobby.tsx index 9f495089..4d495226 100644 --- a/frontend/src/views/Lobby.tsx +++ b/frontend/src/views/Lobby.tsx @@ -237,7 +237,7 @@ function LobbyPage() { dispatch(setCurrentUser(user)); } }); - }, [currentUser, dispatch]); + }, [currentUser, modifiedProblems, dispatch]); // Map the room in Redux to the state variables used in this file useEffect(() => { From 675c195177e66dfe3af76a221dd8f06fc3e3a261 Mon Sep 17 00:00:00 2001 From: Alan Bi Date: Sat, 3 Jul 2021 20:42:10 -0700 Subject: [PATCH 19/19] Discard problem changes if not saved --- frontend/src/views/Lobby.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/views/Lobby.tsx b/frontend/src/views/Lobby.tsx index 4d495226..24f2a9b1 100644 --- a/frontend/src/views/Lobby.tsx +++ b/frontend/src/views/Lobby.tsx @@ -468,6 +468,13 @@ function LobbyPage() { setModifiedProblems(true); }; + // If user clicks close or outside of problem selection modal, discard changes + const closeModalWithoutSaving = () => { + setTempSelectedProblems([]); + setModifiedProblems(false); + setShowProblemSelector(false); + }; + const onSizeSliderChange = (e: React.ChangeEvent) => { const { value } = e.target; @@ -679,7 +686,7 @@ function LobbyPage() { /> setShowProblemSelector(false)} + onExit={closeModalWithoutSaving} fullScreen > @@ -823,7 +830,7 @@ function LobbyPage() { )} - {isHost(currentUser) ? ( + {isHost(currentUser) && !loading ? ( setShowProblemSelector(true)}> {selectedProblems.length ? 'Edit problem selection ' : 'Or choose a specific problem '}