Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -1,30 +1,66 @@
import React, { memo } from 'react';
import React, { memo, useRef, useEffect } from 'react';

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

import '../initEditor';
import languages from '../config/languages';
import useEditor from '../utils/useEditor';

import EditorLoading from './EditorLoading';

function Editor(props) {
const {
value,
syntax,
onChange,
theme,
loading = false,
} = props;
value, syntax, onChange, theme, loading = false, mode,
} = props;

// Map your custom language key to an actual Monaco recognized language
const mappedSyntax = languages[syntax];

// Hooks from your custom editor config
const {
options,
handleEditorDidMount,
handleEditorDidMount: originalEditorDidMount,
handleEditorWillMount,
} = useEditor(props);

// Create a ref for the actual Monaco editor instance
const editorRef = useRef(null);
// Create a ref for the Vim status element
const vimStatusRef = useRef(null);
// Create a ref to hold the vimMode controller so we can dispose if needed
const vimModeRef = useRef(null);

// Wrap your existing "didMount" to store editor and call original if needed
function handleEditorDidMount(editor, monaco) {
editorRef.current = editor;

if (typeof originalEditorDidMount === 'function') {
originalEditorDidMount(editor, monaco);
}
}
// Whenever `mode` changes, enable or disable vimMode
useEffect(() => {
// If we haven't mounted the editor yet, exit
if (!editorRef.current) return;

if (mode === 'vim') {
// If not already in Vim mode, enable it
if (!vimModeRef.current) {
vimModeRef.current = initVimMode(
editorRef.current,
vimStatusRef.current,
);
}
} else if (vimModeRef.current) {
// If we're switching away from Vim mode, dispose it
vimModeRef.current.dispose();
vimModeRef.current = null;
}
/* eslint-disable react-hooks/exhaustive-deps */
}, [mode, editorRef.current]);

return (
<>
<MonacoEditor
Expand All @@ -39,6 +75,17 @@ function Editor(props) {
onChange={onChange}
data-guide-id="Editor"
/>

{/* This is for displaying normal/insert mode status in Vim */}
<div
ref={vimStatusRef}
style={{
padding: '4px 8px',
fontFamily: 'monospace',
borderTop: '1px solid #eaeaea',
}}
/>

<EditorLoading loading={loading} />
</>
);
Expand All @@ -49,6 +96,7 @@ Editor.propTypes = {
syntax: PropTypes.string,
onChange: PropTypes.func.isRequired,
theme: PropTypes.string.isRequired,
mode: PropTypes.string.isRequired,
loading: PropTypes.bool,
wordWrap: PropTypes.string,
lineNumbers: PropTypes.string,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default {
default: 'default',
// vim: 'vim',
vim: 'vim',
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import React, {
useEffect,
useContext,
useCallback,
useRef,
useEffect, useContext, useCallback, useRef,
} from 'react';

import { useInterpret } from '@xstate/react';
Expand Down Expand Up @@ -42,21 +39,27 @@ const restrictedText = '\n\n\n\t"Only for Premium subscribers"';
const useEditorChannelSubscription = (mainService, editorService, player) => {
const dispatch = useDispatch();

const inTestingRoom = useMachineStateSelector(mainService, inTestingRoomSelector);
const inTestingRoom = useMachineStateSelector(
mainService,
inTestingRoomSelector,
);
const isPreview = useMachineStateSelector(mainService, inPreviewRoomSelector);

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

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

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

const clearEditorListeners = GameActions.connectToEditor(editorService, player?.isBanned)(dispatch);
const clearEditorListeners = GameActions.connectToEditor(
editorService,
player?.isBanned,
)(dispatch);

return clearEditorListeners;
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -86,58 +89,89 @@ function EditorContainer({
const subscriptionType = useSelector(selectors.subscriptionTypeSelector);

const currentUserId = useSelector(selectors.currentUserIdSelector);
const currentEditorLangSlug = useSelector(selectors.userLangSelector(currentUserId));
const currentEditorLangSlug = useSelector(
selectors.userLangSelector(currentUserId),
);

const updateEditorValue = useCallback(data => dispatch(GameActions.updateEditorText(data)), [dispatch]);
const updateAndSendEditorValue = useCallback(data => {
dispatch(GameActions.updateEditorText(data));
dispatch(GameActions.sendEditorText(data));
}, [dispatch]);
const updateEditorValue = useCallback(
data => dispatch(GameActions.updateEditorText(data)),
[dispatch],
);
const updateAndSendEditorValue = useCallback(
data => {
dispatch(GameActions.updateEditorText(data));
dispatch(GameActions.sendEditorText(data));
},
[dispatch],
);

const { mainService } = useContext(RoomContext);
const isPreview = useMachineStateSelector(mainService, inPreviewRoomSelector);
const isRestricted = useMachineStateSelector(mainService, isRestrictedContentSelector);
const inTestingRoom = useMachineStateSelector(mainService, inTestingRoomSelector);
const inBuilderRoom = useMachineStateSelector(mainService, inBuilderRoomSelector);
const isActiveGame = useMachineStateSelector(mainService, isGameActiveSelector);
const isRestricted = useMachineStateSelector(
mainService,
isRestrictedContentSelector,
);
const inTestingRoom = useMachineStateSelector(
mainService,
inTestingRoomSelector,
);
const inBuilderRoom = useMachineStateSelector(
mainService,
inBuilderRoomSelector,
);
const isActiveGame = useMachineStateSelector(
mainService,
isGameActiveSelector,
);
const isGameOver = useMachineStateSelector(mainService, isGameOverSelector);
const openedReplayer = useMachineStateSelector(mainService, openedReplayerSelector);
const openedReplayer = useMachineStateSelector(
mainService,
openedReplayerSelector,
);

const isTournamentGame = !!tournamentId;

const context = { userId: id, type, subscriptionType };

const editorService = useInterpret(
editorMachine,
{
context,
devTools: true,
id: `editor_${id}`,
actions: {
userSendSolution: ctx => {
if (ctx.editorState === 'active') {
dispatch(GameActions.checkGameSolution());
}
},
handleTimeoutFailureChecking: ctx => {
dispatch(actions.updateExecutionOutput({
const editorService = useInterpret(editorMachine, {
context,
devTools: true,
id: `editor_${id}`,
actions: {
userSendSolution: ctx => {
if (ctx.editorState === 'active') {
dispatch(GameActions.checkGameSolution());
}
},
handleTimeoutFailureChecking: ctx => {
dispatch(
actions.updateExecutionOutput({
userId: ctx.userId,
status: 'client_timeout',
output: '',
result: {},
asserts: [],
}));
}),
);

dispatch(actions.updateCheckStatus({ [ctx.userId]: false }));
},
dispatch(actions.updateCheckStatus({ [ctx.userId]: false }));
},
},
);
});

const editorCurrent = useMachineStateSelector(editorService, editorStateSelector);
const editorCurrent = useMachineStateSelector(
editorService,
editorStateSelector,
);

const checkActiveTaskSolution = useCallback(() => editorService.send('user_check_solution'), [editorService]);
const checkTestTaskSolution = useCallback(() => dispatch(GameActions.checkTaskSolution(editorService)), [dispatch, editorService]);
const checkActiveTaskSolution = useCallback(
() => editorService.send('user_check_solution'),
[editorService],
);
const checkTestTaskSolution = useCallback(
() => dispatch(GameActions.checkTaskSolution(editorService)),
[dispatch, editorService],
);

const checkResult = inTestingRoom
? checkTestTaskSolution
Expand All @@ -164,7 +198,7 @@ function EditorContainer({
};
}

return () => { };
return () => {};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inTestingRoom]);

Expand Down Expand Up @@ -195,9 +229,13 @@ function EditorContainer({
};

const canChange = userSettings.type === editorUserTypes.currentUser && !openedReplayer;
const editable = !openedReplayer && userSettings.editable && userSettings.editorState !== 'banned';
const editable = !openedReplayer
&& userSettings.editable
&& userSettings.editorState !== 'banned';
const canSendCursor = canChange && !inTestingRoom && !inBuilderRoom;
const updateEditor = editorCurrent.context.editorState === 'testing' ? updateEditorValue : updateAndSendEditorValue;
const updateEditor = editorCurrent.context.editorState === 'testing'
? updateEditorValue
: updateAndSendEditorValue;
const onChange = canChange ? updateEditor : noop;

const editorParams = {
Expand Down Expand Up @@ -228,13 +266,19 @@ function EditorContainer({
'bg-winner': isGameOver && editorCurrent.matches('idle') && isWon,
});

const gameRoomEditorStylesVersion2 = { minHeight: `calc(100vh - 92px - ${toolbarRef.current?.clientHeight || 0}px)` };
const gameRoomEditorStylesVersion2 = {
minHeight: `calc(100vh - 92px - ${toolbarRef.current?.clientHeight || 0}px)`,
};

return (
<div data-editor-state={editorCurrent.value} className={pannelBackground}>
<div
className={`${editorParams.theme === editorThemes.dark ? 'bg-dark ' : 'bg-white '}${cardClassName}`}
style={orientation === 'side' ? gameRoomEditorStylesVersion2 : gameRoomEditorStyles}
style={
orientation === 'side'
? gameRoomEditorStylesVersion2
: gameRoomEditorStyles
}
data-guide-id={orientation === 'left' ? 'LeftEditor' : ''}
>
<EditorToolbar
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import GameActionButtons from './GameActionButtons';
import GameResultIcon from './GameResultIcon';
import TournamentUserGameScore from './TournamentUserGameScore';
import UserGameScore from './UserGameScore';
import VimModeButton from './VimModeButton';

const ModeButtons = ({ player }) => (
<div
className="btn-group align-items-center mr-auto"
role="group"
aria-label="Editor mode"
>
<VimModeButton playerId={player.id} />
<DarkModeButton playerId={player.id} />
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';

import cn from 'classnames';
// import { Col } from 'react-bootstrap';
import { useDispatch, useSelector } from 'react-redux';

import editorModes from '../../config/editorModes';
Expand All @@ -10,23 +11,32 @@ import { actions } from '../../slices';
function VimModeButton() {
const dispatch = useDispatch();
const currentMode = useSelector(editorsModeSelector);

const isVimMode = currentMode === editorModes.vim;

const mode = isVimMode ? editorModes.default : editorModes.vim;
const handleToggleVimMode = () => {
dispatch(
actions.setEditorsMode(isVimMode ? editorModes.default : editorModes.vim),
);
};

// Use meaningful text, not just color, to indicate state
const buttonText = isVimMode ? 'Vim' : 'Vim';

// Keep styling if desired, but ensure text clarifies the mode
const classNames = cn('btn btn-sm rounded-left', {
'btn-light': !isVimMode,
'btn-secondary': isVimMode,
});

const handleToggleVimMode = () => {
dispatch(actions.setEditorsMode(mode));
};

return (
<button type="button" className={classNames} onClick={handleToggleVimMode}>
Vim
<button
type="button"
className={classNames}
onClick={handleToggleVimMode}
aria-pressed={isVimMode}
title={isVimMode ? 'Disable Vim mode' : 'Enable Vim mode'}
>
{buttonText}
</button>
);
}
Expand Down
Loading
Loading