Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#158 add codemirror in new text section #164

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
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';
Franlop7 marked this conversation as resolved.
Show resolved Hide resolved
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
Franlop7 marked this conversation as resolved.
Show resolved Hide resolved
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'
Franlop7 marked this conversation as resolved.
Show resolved Hide resolved
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([
Franlop7 marked this conversation as resolved.
Show resolved Hide resolved
{ 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
Franlop7 marked this conversation as resolved.
Show resolved Hide resolved
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) => {
Franlop7 marked this conversation as resolved.
Show resolved Hide resolved
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} />
Franlop7 marked this conversation as resolved.
Show resolved Hide resolved
<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\`\`\``;
Franlop7 marked this conversation as resolved.
Show resolved Hide resolved

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) => {
Franlop7 marked this conversation as resolved.
Show resolved Hide resolved
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';
Franlop7 marked this conversation as resolved.
Show resolved Hide resolved
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"
Franlop7 marked this conversation as resolved.
Show resolved Hide resolved
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