Skip to content

Commit

Permalink
upgrades and fix cursor selection after changing editor options
Browse files Browse the repository at this point in the history
  • Loading branch information
ReDBrother committed Oct 22, 2024
1 parent fc972a3 commit ce14c2d
Show file tree
Hide file tree
Showing 15 changed files with 341 additions and 281 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import waitingRoom from '../widgets/machines/waitingRoom';
import RootContainer from '../widgets/pages/RoomWidget';
import reducers from '../widgets/slices';

jest.mock('../widgets/initEditor.js', () => ({}));

jest.mock('../widgets/pages/game/TaskDescriptionMarkdown', () => () => (<>Examples: </>));

jest.mock('@fortawesome/react-fontawesome', () => ({
Expand Down Expand Up @@ -77,7 +79,7 @@ jest.mock(

jest.mock(
'../widgets/utils/useStayScrolled',
() => () => ({ stayScrolled: () => {} }),
() => () => ({ stayScrolled: () => { } }),
{ virtual: true },
);

Expand Down Expand Up @@ -105,7 +107,7 @@ jest.mock(

return channel;
}),
connect: jest.fn(() => {}),
connect: jest.fn(() => { }),
})),
};
},
Expand Down Expand Up @@ -161,8 +163,8 @@ const preloadedState = {
},
},
usersInfo: {
1: { },
2: { },
1: {},
2: {},
},
chat: {
users: Object.values(players),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,68 +1,47 @@
import React, { memo } from 'react';

import MonacoEditor, { loader } from '@monaco-editor/react';
import '../initEditor';
import MonacoEditor from '@monaco-editor/react';
import PropTypes from 'prop-types';

import haskellProvider from '../config/editor/haskell';
import sassProvider from '../config/editor/sass';
import stylusProvider from '../config/editor/stylus';
import languages from '../config/languages';
import useEditor from '../utils/useEditor';

import EditorLoading from './EditorLoading';

const monacoVersion = '0.52.0';

loader.config({
paths: {
vs: `https://cdn.jsdelivr.net/npm/monaco-editor@${monacoVersion}/min/vs`,
},
});

loader.init().then(monaco => {
monaco.languages.register({ id: 'haskell', aliases: ['haskell'] });
monaco.languages.setMonarchTokensProvider('haskell', haskellProvider);

monaco.languages.register({ id: 'stylus', aliases: ['stylus'] });
monaco.languages.setMonarchTokensProvider('stylus', stylusProvider);

monaco.languages.register({ id: 'scss', aliases: ['scss'] });
monaco.languages.setMonarchTokensProvider('scss', sassProvider);
});

function Editor(props) {
const {
value,
syntax,
onChange,
theme,
loading = false,
} = props;
const mappedSyntax = languages[syntax];
const {
value,
syntax,
onChange,
theme,
loading = false,
} = props;
const mappedSyntax = languages[syntax];

const {
options,
handleEditorDidMount,
handleEditorWillMount,
} = useEditor(props);
const {
options,
handleEditorDidMount,
handleEditorWillMount,
} = useEditor(props);

return (
<>
<MonacoEditor
theme={theme}
options={options}
width="100%"
height="100%"
language={mappedSyntax}
beforeMount={handleEditorWillMount}
onMount={handleEditorDidMount}
value={value}
onChange={onChange}
data-guide-id="Editor"
/>
<EditorLoading loading={loading} />
</>
);
return (
<>
<MonacoEditor
theme={theme}
options={options}
width="100%"
height="100%"
language={mappedSyntax}
beforeMount={handleEditorWillMount}
onMount={handleEditorDidMount}
value={value}
onChange={onChange}
data-guide-id="Editor"
/>
<EditorLoading loading={loading} />
</>
);
}

Editor.propTypes = {
Expand All @@ -75,7 +54,7 @@ Editor.propTypes = {
lineNumbers: PropTypes.string,
fontSize: PropTypes.number,
editable: PropTypes.bool,
gameMode: PropTypes.string.isRequired,
roomMode: PropTypes.string.isRequired,
checkResult: PropTypes.func.isRequired,
toggleMuteSound: PropTypes.func.isRequired,
mute: PropTypes.bool.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const mapStateToProps = state => {
const locked = gameLockedSelector(state);
return {
gameId,
gameMode,
roomMode: gameMode,
locked,
mute: state.user.settings.mute,
};
Expand Down
26 changes: 26 additions & 0 deletions services/app/apps/codebattle/assets/js/widgets/initEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { loader } from '@monaco-editor/react';
import * as monacoLib from 'monaco-editor';

import haskellProvider from './config/editor/haskell';
import sassProvider from './config/editor/sass';
import stylusProvider from './config/editor/stylus';

// const monacoVersion = '0.52.0';

loader.config({
monaco: monacoLib,
// paths: {
// vs: `https://cdn.jsdelivr.net/npm/monaco-editor@${monacoVersion}/min/vs`,
// },
});

loader.init().then(monaco => {
monaco.languages.register({ id: 'haskell', aliases: ['haskell'] });
monaco.languages.setMonarchTokensProvider('haskell', haskellProvider);

monaco.languages.register({ id: 'stylus', aliases: ['stylus'] });
monaco.languages.setMonarchTokensProvider('stylus', stylusProvider);

monaco.languages.register({ id: 'scss', aliases: ['scss'] });
monaco.languages.setMonarchTokensProvider('scss', sassProvider);
});
18 changes: 14 additions & 4 deletions services/app/apps/codebattle/assets/js/widgets/middlewares/Room.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,19 @@ export const resetTextToTemplateAndSend = langSlug => (dispatch, getState) => {

export const soundNotification = notification();

export const addCursorListeners = (userId, onChangePosition, onChangeSelection) => {
if (!userId || isRecord) {
return () => {};
export const addCursorListeners = (params, onChangePosition, onChangeSelection) => {
const {
roomMode,
userId,
} = params;

const isBuilder = roomMode === GameRoomModes.builder;
const isHistory = roomMode === GameRoomModes.history;

const canReceivedRemoteCursor = !isBuilder && !isHistory && !!userId && !isRecord;

if (!canReceivedRemoteCursor) {
return () => { };
}

const handleNewCursorPosition = debounce(data => {
Expand Down Expand Up @@ -1039,7 +1049,7 @@ export const changePlaybookSolution = method => dispatch => {
export const storedEditorReady = service => {
service.send('load_stored_editor');

return () => {};
return () => { };
};

export const downloadPlaybook = service => dispatch => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import cn from 'classnames';
import { useDispatch, useSelector } from 'react-redux';

Expand All @@ -15,7 +16,7 @@ function DakModeButton() {
const isDarkMode = currentTheme === editorThemes.dark;
const mode = isDarkMode ? editorThemes.light : editorThemes.dark;

const classNames = cn('btn btn-sm mr-2 border rounded', {
const className = cn('btn mr-2 border rounded', {
'btn-light': isDarkMode,
'btn-secondary': !isDarkMode,
});
Expand All @@ -25,8 +26,8 @@ function DakModeButton() {
};

return (
<button type="button" className={classNames} onClick={handleToggleDarkMode}>
{isDarkMode ? 'Light' : 'Dark'}
<button type="button" className={className} onClick={handleToggleDarkMode}>
<FontAwesomeIcon icon={isDarkMode ? 'sun' : 'moon'} />
</button>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ const useEditorChannelSubscription = (mainService, editorService, player) => {

useEffect(() => {
if (isPreview) {
return () => {};
return () => { };
}

if (inTestingRoom) {
editorService.send('load_testing_editor');

return () => {};
return () => { };
}

const clearEditorListeners = GameActions.connectToEditor(editorService, player?.isBanned)(dispatch);
Expand Down Expand Up @@ -199,19 +199,17 @@ function EditorContainer({
const canSendCursor = canChange && !inTestingRoom && !inBuilderRoom;
const updateEditor = editorCurrent.context.editorState === 'testing' ? updateEditorValue : updateAndSendEditorValue;
const onChange = canChange ? updateEditor : noop;
const onChangeCursorSelection = canSendCursor ? GameActions.sendEditorCursorSelection : noop;
const onChangeCursorPosition = canSendCursor ? GameActions.sendEditorCursorPosition : noop;

const editorParams = {
roomMode: tournamentId ? GameModeCodes.tournament : gameMode,
userId: id,
wordWrap: 'off',
lineNumbers: 'on',
hidingPanelControls: false,
userType: type,
syntax: editorState?.currentLangSlug || 'js',
onChange,
onChangeCursorSelection,
onChangeCursorPosition,
canSendCursor,
checkResult,
value: isRestricted ? restrictedText : editorState?.text,
editorHeight,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {
useMemo, useState, useEffect, useCallback,
} from 'react';

import pick from 'lodash/pick';

import editorUserTypes from '../config/editorUserTypes';
import * as RoomActions from '../middlewares/Room';

const useCursorUpdates = (editor, monaco, props) => {
const params = useMemo(
() => pick(props, ['userId', 'roomMode']),

// eslint-disable-next-line react-hooks/exhaustive-deps
[props.userId, props.roomMode],
);
const [, setRemoteKeys] = useState([]);
const [remote, setRemote] = useState({
cursor: {},
selection: {},
});

const updateRemoteCursorPosition = useCallback(offset => {
const { readOnly, userType } = editor.getRawOptions();

const position = editor.getModel().getPositionAt(offset);
const userClassName = userType === editorUserTypes.opponent
? 'cb-remote-opponent'
: 'cb-remote-player';

if (readOnly) {
const cursor = {
range: new monaco.Range(
position.lineNumber,
position.column,
position.lineNumber,
position.column,
),
options: { className: `cb-editor-remote-cursor ${userClassName}` },
};

setRemote(oldRemote => ({
...oldRemote,
cursor,
}));
}
}, [setRemote, editor, monaco]);

const updateRemoteCursorSelection = useCallback((startOffset, endOffset) => {
const { readOnly, userType } = editor.getRawOptions();

const userClassName = userType === editorUserTypes.opponent
? 'cb-remote-opponent'
: 'cb-remote-player';

if (readOnly) {
const startPosition = editor.getModel().getPositionAt(startOffset);
const endPosition = editor.getModel().getPositionAt(endOffset);

const startColumn = startPosition.column;
const startLineNumber = startPosition.lineNumber;
const endColumn = endPosition.column;
const endLineNumber = endPosition.lineNumber;

const selection = {
range: new monaco.Range(
startLineNumber,
startColumn,
endLineNumber,
endColumn,
),
options: { className: `cb-editor-remote-selection ${userClassName}` },
};

setRemote(prevRemote => ({
...prevRemote,
selection,
}));
}
}, [setRemote, editor, monaco]);

useEffect(() => {
if (remote.cursor.range && remote.selection.range) {
setRemoteKeys(oldRemoteKeys => (
editor.deltaDecorations(oldRemoteKeys, Object.values(remote))
));
}
}, [editor, remote, setRemoteKeys]);

useEffect(() => {
const clearCursorListeners = RoomActions.addCursorListeners(
params,
updateRemoteCursorPosition,
updateRemoteCursorSelection,
);

return clearCursorListeners;
}, [
params,
updateRemoteCursorPosition,
updateRemoteCursorSelection,
]);
};

export default useCursorUpdates;
Loading

0 comments on commit ce14c2d

Please sign in to comment.