Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions front/global.types.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
declare module '*.png';
declare module '*.svg';
declare module '*.css';
2,829 changes: 2,795 additions & 34 deletions front/package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,21 @@
"license": "MIT",
"homepage": "",
"dependencies": {
"@codemirror/language-data": "^6.1.0",
"@emotion/css": "^11.10.5",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@mui/icons-material": "^5.11.0",
"@mui/material": "^5.11.8",
"axios": "^1.3.2",
"codemirror": "^6.0.1",
"lodash.merge": "^4.6.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^8.0.6",
"react-router-dom": "^6.8.1",
"regenerator-runtime": "^0.13.11",
"remark-gfm": "^3.0.1",
"socket.io": "^2.5.0"
},
"devDependencies": {
Expand Down
79 changes: 79 additions & 0 deletions front/src/common/markdowneditor/markdowneditor.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from "react";
import { basicSetup, EditorView } from 'codemirror'
import { EditorState } from '@codemirror/state'
import { useEffect, useRef } from 'react'
import { markdown } from '@codemirror/lang-markdown'
import { languages } from '@codemirror/language-data'
import { tags } from '@lezer/highlight'
import { syntaxHighlighting, HighlightStyle} from '@codemirror/language'
import * as classes from './markdowneditor.styles';

const stylesEditor = HighlightStyle.define([
{ tag: tags.heading1, class: classes.headerH1},
{ tag: tags.heading2, class: classes.headerH2},
{ tag: tags.heading3, class: classes.headerH3},
]);

interface Props {
value: string
onChange?: (value: string) => void
className?: string
}

export const MarkdownEditor: React.FC<Props> = (props) => {

const refContainer = useRef(null)
const editorView = useRef<EditorView>()

useEffect(() => {
if (!refContainer.current) return

editorView.current = new EditorView({
state: EditorState.create({
doc: props.value,
extensions: [
basicSetup,
markdown({
codeLanguages: languages,
}),
syntaxHighlighting(stylesEditor),
EditorView.lineWrapping,
EditorView.updateListener.of((update) => {
props.onChange(update.state.doc.toString())
}),
EditorView.theme({
'&': {
border: '2px solid #070707',
height: '300px',
},
}),
]
}),
parent: refContainer.current
});

return () => editorView.current?.destroy()
}, []);

const scrollToEnd = () => {
if (!refContainer.current) return;

const scrollHeight = refContainer.current.scrollHeight;
const clientHeight = refContainer.current.clientHeight;
editorView.current.scrollDOM.scrollTop = scrollHeight - clientHeight;
}

useEffect(() => {
const state = editorView.current?.state
const currentValue = state?.doc.toString()
if (state && currentValue !== props.value) {
const update = state.update({
changes: { from: 0, to: state.doc.length, insert: props.value }
});
editorView.current?.update([update]);
scrollToEnd();
}
}, [props.value]);

return <div ref={refContainer} className={ props.className } />
};
13 changes: 13 additions & 0 deletions front/src/common/markdowneditor/markdowneditor.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { css } from '@emotion/css';

export const headerH1 = css`
font-size: 2.3rem;
`;

export const headerH2 = css`
font-size: 1.8rem;
`;

export const headerH3 = css`
font-size: 1.3rem;
`;
16 changes: 16 additions & 0 deletions front/src/common/markdownview/markdownView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';

interface Props {
value: string
className?: string;
}

export const MarkdownView: React.FC<Props> = (props) => {
return (
<div className={ props.className }>
<ReactMarkdown children={props.value} remarkPlugins={[remarkGfm]} />
</div>
);
};
23 changes: 5 additions & 18 deletions front/src/pods/student/student.component.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import React from 'react';
import TextareaAutosize from '@mui/material/TextareaAutosize';
import Typography from '@mui/material/Typography';

import Checkbox from '@mui/material/Checkbox';
import FormControlLabel from '@mui/material/FormControlLabel';

import * as innerClasses from './student.styles';
import { useAutoScroll } from 'common/hooks/auto-scroll.hook';
import { MarkdownView } from 'common/markdownview/markdownView';
import * as innerClasses from './student.styles';

interface Props {
room: string;
log: string;
className?: string;
}

export const StudentComponent: React.FC<Props> = props => {
Expand All @@ -19,7 +18,6 @@ export const StudentComponent: React.FC<Props> = props => {
const {
isAutoScrollEnabled,
setIsAutoScrollEnabled,
textAreaRef,
doAutoScroll,
} = useAutoScroll();

Expand All @@ -40,20 +38,9 @@ export const StudentComponent: React.FC<Props> = props => {
<label className={innerClasses.label} htmlFor="session">
Content
</label>
<div role="log">
<TextareaAutosize
ref={textAreaRef}
data-testid="session"
id="session"
maxRows={30}
minRows={30}
className={innerClasses.textarea}
value={log ?? ''}
readOnly={true}
/>
</div>
<MarkdownView value={log ?? ''} className={innerClasses.textView} />
<FormControlLabel
label="Disable AutoScroll"
label="Enable AutoScroll"
control={
<Checkbox
checked={isAutoScrollEnabled}
Expand Down
4 changes: 3 additions & 1 deletion front/src/pods/student/student.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ export const label = css`
font-size: 1.125rem;
`;

export const textarea = css`
export const textView = css`
width: 100%;
height: 600px;
overflow: auto;
box-sizing: border-box;
padding: ${spacing(2)};
font-family: ${typography.fontFamily};
Expand Down
34 changes: 16 additions & 18 deletions front/src/pods/trainer/components/new-text.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,44 @@ import React from 'react';
import { cx } from '@emotion/css';
import ArrowForwardRoundedIcon from '@mui/icons-material/ArrowForwardRounded';
import Button from '@mui/material/Button';
import TextareaAutosize from '@mui/material/TextareaAutosize';
import * as innerClasses from './new-text.styles';
import { SelectComponent } from './select.component';
import { MarkdownEditor } from 'common/markdowneditor/markdowneditor.component';

interface Props {
handleAppendTrainerText: (trainerText: string) => void;
className?: string;
}

export const NewTextComponent: React.FC<Props> = (props) => {
const { handleAppendTrainerText, className } = props;
const [language, setLanguage] = React.useState('');
const [trainerText, setTrainerText] = React.useState<string>('');

const languageModify = (language: string): string => language === "" ? "" : `\`\`\`${language}\n\n\`\`\``;

const { handleAppendTrainerText, className } = props;

const handleAppendTrainerTextInternal = (): void => {
if (trainerText) {
handleAppendTrainerText(trainerText);
setTrainerText('');
setTrainerText(languageModify(language));
setLanguage(language);
}
};

const handleOnChange = (e: React.ChangeEvent<HTMLTextAreaElement>): void => {
setTrainerText(e.target.value);
};
React.useEffect(() => {
if (language) {
setTrainerText(languageModify(language));
}
},[language]);

return (
<form className={cx(innerClasses.root, className)}>
<label className={innerClasses.label} htmlFor="new-text">
New text
</label>
<TextareaAutosize
maxRows={10}
minRows={10}
className={innerClasses.textarea}
onChange={(e) => handleOnChange(e)}
onKeyDown={(event) => {
if (event.key === 'Enter' && event.ctrlKey) {
handleAppendTrainerTextInternal();
}
}}
value={trainerText}
/>
<SelectComponent value={language} onChange={setLanguage} />
<MarkdownEditor value={trainerText} onChange={setTrainerText} />
<Button
variant="contained"
color="primary"
Expand Down
34 changes: 34 additions & 0 deletions front/src/pods/trainer/components/select.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import InputLabel from '@mui/material/InputLabel';
import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select';
import { MenuItem } from '@mui/material';
import { languageFormat } from '../trainer.constants';

interface Props {
onChange: (value: string) => void;
value: string;
}

export const SelectComponent: React.FC<Props> = (props) => {
const {onChange,value} = props;

const handleSelectChange = event => {
onChange(event.target.value as string);
};

return (
<FormControl variant="outlined">
<InputLabel htmlFor="outlined-lang-native-simple">Language</InputLabel>
<Select
label="Language"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove htmlFor

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or try add id="select" and change htmlFor="select"

value={value}
onChange={handleSelectChange}
>
{languageFormat.map(language =>
<MenuItem key={language.id} value={language.id}>{language.label}</MenuItem>
)}
</Select>
</FormControl>
);
};
59 changes: 25 additions & 34 deletions front/src/pods/trainer/components/session.component.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import React from 'react';
import { cx } from '@emotion/css';
import TextareaAutosize from '@mui/material/TextareaAutosize';
import ArrowForwardRoundedIcon from '@mui/icons-material/ArrowForwardRounded';
import UndoIcon from '@mui/icons-material/Undo';
import Button from '@mui/material/Button';

import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';

import * as innerClasses from './session.styles';
import { useAutoScroll } from 'common/hooks/auto-scroll.hook';
import { MarkdownEditor } from 'common/markdowneditor/markdowneditor.component';
import * as innerClasses from './session.styles';

interface Props {
log: string;
handleSendFullContentLog: (fullContent: string) => void;
Expand Down Expand Up @@ -37,7 +36,6 @@ export const SessionComponent: React.FC<Props> = (props) => {
const {
isAutoScrollEnabled,
setIsAutoScrollEnabled,
textAreaRef,
doAutoScroll,
} = useAutoScroll();

Expand All @@ -51,35 +49,7 @@ export const SessionComponent: React.FC<Props> = (props) => {
<label className={innerClasses.label} htmlFor="session">
Session
</label>

<TextareaAutosize
role="log"
ref={textAreaRef}
id="session"
maxRows={20}
minRows={20}
className={innerClasses.textarea}
/>
<FormControlLabel
label="Disable AutoScroll"
control={
<Checkbox
checked={isAutoScrollEnabled}
onChange={(e) => setIsAutoScrollEnabled(e.target.checked)}
color="primary"
/>
}
/>
<Button
variant="contained"
color="secondary"
disableElevation
className={innerClasses.undoButton}
onClick={() => handleSetSessionContent(log)}
>
<UndoIcon className={innerClasses.undoIcon} />
Undo
</Button>
<MarkdownEditor value={log} onChange={handleSendFullContentLog} className={innerClasses.textEditor} />
<Button
variant="contained"
color="primary"
Expand All @@ -90,6 +60,27 @@ export const SessionComponent: React.FC<Props> = (props) => {
Send Full Content
<ArrowForwardRoundedIcon className={innerClasses.sendIcon} />
</Button>
<Button
variant="contained"
color="secondary"
disableElevation
className={innerClasses.undoButton}
onClick={() => handleSetSessionContent(log)}
>
<UndoIcon className={innerClasses.undoIcon} />
Undo
</Button>
<FormControlLabel
className={innerClasses.autoScroll}
label="Enable AutoScroll"
control={
<Checkbox
checked={isAutoScrollEnabled}
onChange={(e) => setIsAutoScrollEnabled(e.target.checked)}
color="primary"
/>
}
/>
</form>
);
};
Loading