Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions frontend/src/components/core/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/core/HelpModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export function LobbyHelpModal(props: HelpModalProps) {
<HelpText>
<b>Difficulty</b>
: 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.
</HelpText>
</>
<>
Expand Down
27 changes: 14 additions & 13 deletions frontend/src/components/core/Icon.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import styled from 'styled-components';

export const InlineIcon = styled.i.attrs(() => ({
Expand Down Expand Up @@ -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;
Expand All @@ -70,3 +59,15 @@ export const SpectatorBackIcon = styled.i.attrs(() => ({
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.24);
}
`;

export const PrevIcon = () => (
<i className="material-icons">
navigate_before
</i>
);

export const NextIcon = () => (
<i className="material-icons">
navigate_next
</i>
);
2 changes: 1 addition & 1 deletion frontend/src/components/game/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
105 changes: 21 additions & 84 deletions frontend/src/components/game/PlayerGameView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 {
Expand All @@ -33,34 +31,13 @@ import {
Player,
} from '../../api/Game';
import LeaderboardCard from '../card/LeaderboardCard';
import { getDifficultyDisplayButton, InheritedTextButton, 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;
Expand All @@ -70,7 +47,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
Expand Down Expand Up @@ -158,7 +135,6 @@ function PlayerGameView(props: PlayerGameViewProps) {

const { currentUser, game } = useAppSelector((state) => state);

const [copiedEmail, setCopiedEmail] = useState(false);
const [submissions, setSubmissions] = useState<Submission[]>([]);

const [problems, setProblems] = useState<Problem[]>([]);
Expand Down Expand Up @@ -428,19 +404,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) => (
Expand Down Expand Up @@ -509,45 +487,13 @@ function PlayerGameView(props: PlayerGameViewProps) {
secondaryMinSize={35}
customClassName={!spectateGame ? 'game-splitter-container' : undefined}
>
{/* Problem title/description panel */}
<OverflowPanel className="display-box-shadow">
<ProblemHeaderText>
{!spectateGame
? game?.problems[currentProblemIndex]?.name
: spectateGame?.problem.name}
</ProblemHeaderText>
{
!spectateGame ? (
getDifficultyDisplayButton(game?.problems[currentProblemIndex].difficulty!)
) : (
getDifficultyDisplayButton(spectateGame?.problem.difficulty!)
)
}
<StyledMarkdownEditor
defaultValue={!spectateGame ? (
game?.problems[0]?.description
) : (
spectateGame?.problem.description
)}
value={spectateGame
? spectateGame?.problem.description
: problems[currentProblemIndex]?.description}
onChange={() => ''}
readOnly
/>
<BottomFooterText>
{'Notice an issue? Contact us at '}
<InheritedTextButton
onClick={() => {
copy('[email protected]');
setCopiedEmail(true);
}}
>
[email protected]
<InlineCopyIcon>content_copy</InlineCopyIcon>
</InheritedTextButton>
</BottomFooterText>
</OverflowPanel>
<ProblemPanel
problems={game?.problems || []}
index={!spectateGame ? currentProblemIndex : game?.problems
.findIndex((p) => p.problemId === spectateGame.problem.problemId) || 0}
onNext={currentProblemIndex < problems.length - 1 ? nextProblem : null}
onPrev={currentProblemIndex > 0 ? previousProblem : null}
/>

{/* Code editor and console panels */}
{
Expand Down Expand Up @@ -580,7 +526,7 @@ function PlayerGameView(props: PlayerGameViewProps) {
</Panel>
</SplitterLayout>
) : (
<NoPaddingPanel className="display-box-shadow">
<NoPaddingPanel>
<Editor
onLanguageChange={null}
onCodeChange={null}
Expand All @@ -596,15 +542,6 @@ function PlayerGameView(props: PlayerGameViewProps) {
}
</SplitterLayout>
</SplitterContainer>

<SmallButton onClick={previousProblem}>Previous</SmallButton>
<SmallButton onClick={nextProblem}>Next</SmallButton>

<BottomCopyIndicatorContainer copied={copiedEmail}>
<CopyIndicator onClick={() => setCopiedEmail(false)}>
Email copied!&nbsp;&nbsp;✕
</CopyIndicator>
</BottomCopyIndicatorContainer>
</>
);
}
Expand Down
121 changes: 121 additions & 0 deletions frontend/src/components/game/ProblemPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React from 'react';
import styled from 'styled-components';
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,
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;
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;
`;

const ProblemNavContainer = styled(FlexRight)`
align-items: baseline;
padding: 15px 0;
`;

const ProblemCountText = styled(SmallText)`
color: gray;
`;

type ProblemNavButtonProps = {
disabled: boolean,
};

const ProblemNavButton = styled(DefaultButton)<ProblemNavButtonProps>`
font-size: ${({ theme }) => theme.fontSize.default};
color: ${({ theme }) => theme.colors.gray};
background-color: ${({ theme, disabled }) => (disabled ? theme.colors.background : theme.colors.white)};
border-radius: 5px;
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 = {
problems: Problem[],
index: number,
onNext: (() => void) | null,
onPrev: (() => void) | null,
};

function ProblemPanel(props: ProblemPanelProps) {
const {
problems, index, onNext, onPrev,
} = props;

return (
<OverflowPanel>
<FlexHorizontalContainer>
<FlexLeft>
<div>
<ProblemHeaderText>{problems[index]?.name || 'Loading...'}</ProblemHeaderText>
{problems[index] ? getDifficultyDisplayButton(problems[index].difficulty) : null}
</div>
</FlexLeft>
<ProblemNavContainer>
<CenteredContainer>
<div>
<ProblemNavButton onClick={onPrev || undefined} disabled={!onPrev}>
<PrevIcon />
</ProblemNavButton>
<ProblemNavButton onClick={onNext || undefined} disabled={!onNext}>
<NextIcon />
</ProblemNavButton>
</div>
<ProblemCountText>
{`Problem ${index + 1} of ${problems.length}`}
</ProblemCountText>
</CenteredContainer>
</ProblemNavContainer>
</FlexHorizontalContainer>

<StyledMarkdownEditor
defaultValue={problems[index]?.description || ''}
value={problems[index]?.description || ''}
onChange={() => ''}
readOnly
/>
<BottomFooterText>
Notice an issue? Contact us at
{' '}
<Copyable text="[email protected]" top={false} />
</BottomFooterText>
</OverflowPanel>
);
}

export default ProblemPanel;
Loading