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
2,843 changes: 2,802 additions & 41 deletions front/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion 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 Expand Up @@ -71,7 +75,7 @@
"lint-staged": "^13.1.1",
"mini-css-extract-plugin": "^2.7.2",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.4",
"prettier": "^2.8.7",
"pretty-quick": "^3.1.3",
"react-test-renderer": "^18.2.0",
"rimraf": "^4.1.2",
Expand Down
2 changes: 2 additions & 0 deletions front/src/common/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './hooks';
export * from './global-window';
export * from './markdown-editor';
export * from './markdown-view';
1 change: 1 addition & 0 deletions front/src/common/markdown-editor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './markdown-editor.component';
93 changes: 93 additions & 0 deletions front/src/common/markdown-editor/markdown-editor.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react';
import { basicSetup, EditorView } from 'codemirror';
import { EditorState } from '@codemirror/state';
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 './markdown-editor.styles';

const editorStyles = 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;
onAppendTrainerTextInternal?: () => void;
}

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

const refContainer = React.useRef(null);
const editorView = React.useRef<EditorView>();

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

editorView.current = new EditorView({
state: EditorState.create({
doc: value,
extensions: [
basicSetup,
markdown({
codeLanguages: languages,
}),
syntaxHighlighting(editorStyles),
EditorView.lineWrapping,
EditorView.updateListener.of((update) => {
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;
};

React.useEffect(() => {
const state = editorView.current?.state;
const currentValue = state?.doc.toString();
if (state && currentValue !== value) {
const update = state.update({
changes: { from: 0, to: state.doc.length, insert: value },
});
editorView.current?.update([update]);
scrollToEnd();
}
}, [value]);
// TODO HAY QUE ARREGLARLO PARA EL SESION
// ?. si existe llama a la función, de lo contrario, se ignora
return (
<div
role="log"
id="session"
ref={refContainer}
className={className}
onKeyDown={(event) => {
if (event.key === 'Enter' && event.ctrlKey) {
onAppendTrainerTextInternal?.();
}
}}
/>
);
};
13 changes: 13 additions & 0 deletions front/src/common/markdown-editor/markdown-editor.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;
`;
1 change: 1 addition & 0 deletions front/src/common/markdown-view/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './markdown-view';
16 changes: 16 additions & 0 deletions front/src/common/markdown-view/markdown-view.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 MarkdownViewer: React.FC<Props> = (props) => {
return (
<div role="log" className={props.className}>
<ReactMarkdown children={props.value} remarkPlugins={[remarkGfm]} />
</div>
);
};
34 changes: 9 additions & 25 deletions front/src/pods/student/student.component.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
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 { MarkdownViewer } from 'common/markdown-view/markdown-view';
import * as innerClasses from './student.styles';

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

export const StudentComponent: React.FC<Props> = props => {
export const StudentComponent: React.FC<Props> = (props) => {
const { room, log } = props;

const {
isAutoScrollEnabled,
setIsAutoScrollEnabled,
textAreaRef,
doAutoScroll,
} = useAutoScroll();
const { isAutoScrollEnabled, setIsAutoScrollEnabled, doAutoScroll } =
useAutoScroll();

React.useEffect(() => {
doAutoScroll();
Expand All @@ -40,24 +35,13 @@ 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>
<MarkdownViewer value={log ?? ''} className={innerClasses.textView} />
<FormControlLabel
label="Disable AutoScroll"
label="Enable AutoScroll"
control={
<Checkbox
checked={isAutoScrollEnabled}
onChange={e => setIsAutoScrollEnabled(e.target.checked)}
onChange={(e) => setIsAutoScrollEnabled(e.target.checked)}
color="primary"
/>
}
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
35 changes: 19 additions & 16 deletions front/src/pods/trainer/components/new-text.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,48 @@ 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/markdown-editor/markdown-editor.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 applyLanguageSelected = (language: string): string =>
language === '' ? '' : `\`\`\`${language}\n\n\`\`\``;

const { handleAppendTrainerText, className } = props;

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

const handleOnChange = (e: React.ChangeEvent<HTMLTextAreaElement>): void => {
setTrainerText(e.target.value);
};
React.useEffect(() => {
if (language) {
setTrainerText(applyLanguageSelected(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();
}
}}
<SelectComponent value={language} onChange={setLanguage} />
<MarkdownEditor
value={trainerText}
onChange={setTrainerText}
onAppendTrainerTextInternal={handleAppendTrainerTextInternal}
/>
<Button
variant="contained"
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 { FormControl, InputLabel, Select, 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="select">Language</InputLabel>
<Select
id="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>
);
};
Loading