Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -77,7 +77,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,37 +31,16 @@ 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, useGetSubmission } from '../../util/Hook';
import { routes, send, subscribe } from '../../api/Socket';
import { User } from '../../api/User';
import ProblemPanel from './ProblemPanel';
import { useAppSelector, useBestSubmission, useGetSubmission } from '../../util/Hook';
import {
getScore, getSubmissionCount, getSubmissionTime, getSubmission,
} 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;
`;

const NoPaddingPanel = styled(Panel)`
padding: 0;
`;
Expand All @@ -72,7 +49,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 @@ -160,7 +137,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 @@ -383,7 +359,7 @@ function PlayerGameView(props: PlayerGameViewProps) {
// Set the 'test' submission type to correctly display result.
// eslint-disable-next-line no-param-reassign
res.submissionType = SubmissionType.Test;
currentSubmission = res;
currentSubmission = res; // note: this seems a bit improper (fine as long as it works ig)
})
.catch((err) => {
setLoading(false);
Expand Down Expand Up @@ -418,17 +394,19 @@ function PlayerGameView(props: PlayerGameViewProps) {
};

const nextProblem = () => {
setCurrentProblemIndex((currentProblemIndex + 1) % problems?.length);
const next = currentProblemIndex + 1;

if (problems && next < problems.length) {
setCurrentProblemIndex(next);
}
};

const previousProblem = () => {
let temp = currentProblemIndex - 1;
const prev = currentProblemIndex - 1;

if (temp < 0) {
temp += problems?.length;
if (prev >= 0) {
setCurrentProblemIndex(prev);
}

setCurrentProblemIndex(temp);
};

const displayPlayerLeaderboard = useCallback(() => game?.players.map((player, index) => (
Expand Down Expand Up @@ -497,45 +475,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('support@codejoust.co');
setCopiedEmail(true);
}}
>
support@codejoust.co
<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 @@ -567,7 +513,7 @@ function PlayerGameView(props: PlayerGameViewProps) {
</Panel>
</SplitterLayout>
) : (
<NoPaddingPanel className="display-box-shadow">
<NoPaddingPanel>
<Editor
onLanguageChange={null}
onCodeChange={null}
Expand All @@ -582,15 +528,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
134 changes: 134 additions & 0 deletions frontend/src/components/game/ProblemPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
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, 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 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;
`;

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

type ProblemNavButtonProps = {
disabled: boolean,
};

const ProblemNavButton = styled(DefaultButton)<ProblemNavButtonProps>`
font-size: ${({ theme }) => theme.fontSize.default};
color: ${({ theme, disabled }) => (disabled ? theme.colors.lightgray : theme.colors.gray)};
background-color: ${({ theme }) => 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>
<HeaderContainer>
<div>
<TitleContainer>
<ProblemHeaderText>{problems[index]?.name || 'Loading...'}</ProblemHeaderText>
</TitleContainer>
{problems[index] ? getDifficultyDisplayButton(problems[index].difficulty) : null}
</div>
<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>
</HeaderContainer>

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

export default ProblemPanel;
Loading