@elice/material-exercise 1.240619.0 → 1.240620.0-editorevent.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,6 +59,8 @@ export interface MaterialExerciseCommonProps extends MaterialCommponentCommonPro
59
59
  onMultiLangLanguageChange?: MaterialExerciseCommonPropOnMultiLangLanguageChange;
60
60
  onReferenceDocsToggle?: () => void;
61
61
  onCodeHelpRequest?: MaterialExerciseCommonPropOnCodeHelpRequest;
62
+ onCodeEditorChange?: MaterialExerciseCommonPropOnCodeEditorChange;
63
+ onStdioError?: MaterialExerciseCommonPropOnStdioError;
62
64
  onDefaultExerciseRoomIdSet?: MaterialExerciseCommonPropOnDefaultExerciseRoomIdSet;
63
65
  onExerciseRoomIdChange?: MaterialExerciseCommonPropOnExerciseRoomIdChange;
64
66
  onExerciseRunningDone?: MaterialExerciseCommonPropOnRunningDone;
@@ -72,7 +74,7 @@ export type MaterialExerciseCommonPropExerciseImageEmptyOptions = ExerciseProvid
72
74
  /**
73
75
  * On code help button click.
74
76
  */
75
- export type MaterialExerciseCommonPropOnCodeHelpRequest = (params: {
77
+ export type MaterialExerciseCommonPropOnCodeHelpRequest = (payload: {
76
78
  code: string;
77
79
  error?: CodeHelperError;
78
80
  payload?: any;
@@ -80,6 +82,18 @@ export type MaterialExerciseCommonPropOnCodeHelpRequest = (params: {
80
82
  instruction?: string;
81
83
  openFileContent?: string;
82
84
  }) => void;
85
+ /**
86
+ * On code editor drag.
87
+ */
88
+ export type MaterialExerciseCommonPropOnCodeEditorChange = (payload: {
89
+ filename: string;
90
+ content: string;
91
+ selectedContent: string;
92
+ }) => void;
93
+ /**
94
+ * On stdio error.
95
+ */
96
+ export type MaterialExerciseCommonPropOnStdioError = (error: CodeHelperError) => void;
83
97
  /**
84
98
  * On multi language change.
85
99
  */
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var React = require('react');
6
+ var reactUse = require('react-use');
6
7
  var apiClient = require('@elice/api-client');
7
8
  var blocks = require('@elice/blocks');
8
9
  var types = require('@elice/types');
@@ -17,7 +18,6 @@ var FileViewerNonViewable = require('../../shared/file-viewer/FileViewerNonViewa
17
18
  var MonacoEditorLazy = require('../../shared/monaco-editor/MonacoEditorLazy.js');
18
19
  require('../../shared/monaco-editor/MonacoEditorPerferenceForm.js');
19
20
  require('monaco-editor/esm/vs/editor/editor.api');
20
- require('react-use');
21
21
  require('socket.io-client');
22
22
  var recoil$1 = require('../context/recoil.js');
23
23
  var context = require('../context/context.js');
@@ -45,7 +45,8 @@ const ExerciseFileEditor = () => {
45
45
  exerciseRoomId,
46
46
  readOnlyEditor,
47
47
  readOnlyActiveFile,
48
- locale
48
+ locale,
49
+ onCodeEditorChange
49
50
  } = React.useContext(context.ExerciseContext);
50
51
  const exercise = recoil.useRecoilValue(recoil$1.exerciseState(materialExerciseId));
51
52
  const exerciseRoom = recoil.useRecoilValue(recoil$1.exerciseRoomState(exerciseRoomId));
@@ -54,8 +55,9 @@ const ExerciseFileEditor = () => {
54
55
  const activeFilename = recoil.useRecoilValue(recoil$1.exerciseActiveFilenameState);
55
56
  const readOnly = readOnlyEditor || readOnlyActiveFile;
56
57
  const setUsercodeWebSocketState = recoil.useSetRecoilState(recoil$1.exerciseWebsocketQuery('usercodeEdit'));
58
+ const [fileEditorContent, setFileEditorContent] = React.useState(null);
57
59
  const setFileEditorCursorState = recoil.useSetRecoilState(recoil$1.exerciseFileEditorCursorState);
58
- const setFileEditorCursorSelectionValueState = recoil.useSetRecoilState(recoil$1.exerciseFileEditorCursorSelectionValueState);
60
+ const [fileEditorCursorSelectionValue, setFileEditorCursorSelectionValue] = recoil.useRecoilState(recoil$1.exerciseFileEditorCursorSelectionValueState);
59
61
  // editor
60
62
  const editorApis = React.useRef(null);
61
63
  const editorDocHistories = React.useRef({});
@@ -173,6 +175,9 @@ const ExerciseFileEditor = () => {
173
175
  *
174
176
  */
175
177
  const handleChange = e => {
178
+ var _a, _b;
179
+ setFileEditorContent((_b = (_a = editorApis.current) === null || _a === void 0 ? void 0 : _a.getValue()) !== null && _b !== void 0 ? _b : '');
180
+ // === server initiated ===
176
181
  const index = serverInitiatedVersions.current.indexOf(e.versionId - 1);
177
182
  if (index !== -1) {
178
183
  serverInitiatedVersions.current.splice(index, 1);
@@ -210,7 +215,7 @@ const ExerciseFileEditor = () => {
210
215
  wsUsercode.sendCursor(range);
211
216
  }
212
217
  setFileEditorCursorState(range);
213
- setFileEditorCursorSelectionValueState((_b = (_a = editorApis.current) === null || _a === void 0 ? void 0 : _a.getValueInRange(range.from, range.to)) !== null && _b !== void 0 ? _b : '');
218
+ setFileEditorCursorSelectionValue((_b = (_a = editorApis.current) === null || _a === void 0 ? void 0 : _a.getValueInRange(range.from, range.to)) !== null && _b !== void 0 ? _b : '');
214
219
  };
215
220
  /**
216
221
  * Handle editor save shortcut emitted.
@@ -270,6 +275,33 @@ const ExerciseFileEditor = () => {
270
275
  // eslint-disable-next-line react-hooks/exhaustive-deps
271
276
  []);
272
277
  //
278
+ // Emit editor change event to parent component.
279
+ //
280
+ reactUse.useDebounce(() => {
281
+ if (!isReady) {
282
+ return;
283
+ }
284
+ if (!activeFilename || fileEditorContent === null) {
285
+ return;
286
+ }
287
+ if (typeof onCodeEditorChange === 'function') {
288
+ onCodeEditorChange({
289
+ filename: activeFilename,
290
+ content: fileEditorContent,
291
+ selectedContent: fileEditorCursorSelectionValue !== null && fileEditorCursorSelectionValue !== void 0 ? fileEditorCursorSelectionValue : ''
292
+ });
293
+ }
294
+ }, 150, [isReady, activeFilename, fileEditorContent, fileEditorCursorSelectionValue]);
295
+ //
296
+ // Update file content on active file change.
297
+ //
298
+ React.useEffect(() => {
299
+ var _a, _b;
300
+ if (activeFilename && isReady) {
301
+ setFileEditorContent((_b = (_a = editorApis.current) === null || _a === void 0 ? void 0 : _a.getValue()) !== null && _b !== void 0 ? _b : '');
302
+ }
303
+ }, [activeFilename, isReady]);
304
+ //
273
305
  //
274
306
  //
275
307
  // empty file
@@ -51,7 +51,8 @@ const ExerciseRunner = ({
51
51
  }) => {
52
52
  var _a, _b, _c;
53
53
  const {
54
- onCodeHelpRequest
54
+ onCodeHelpRequest,
55
+ onStdioError
55
56
  } = React.useContext(context.ExerciseContext);
56
57
  const {
57
58
  materialExerciseId,
@@ -81,6 +82,9 @@ const ExerciseRunner = ({
81
82
  const handleAiHelpibotError = text => {
82
83
  var _a, _b, _c;
83
84
  const error = runner.getProgrammingErrorResult(text);
85
+ if (!!error && typeof onStdioError === 'function') {
86
+ onStdioError(error);
87
+ }
84
88
  if (!!error && typeof onCodeHelpRequest === 'function') {
85
89
  onCodeHelpRequest({
86
90
  code: error.errorCode,
@@ -2,4 +2,4 @@ export { default as MaterialExercise } from './MaterialExercise';
2
2
  export type { MaterialExerciseProps, MaterialExerciseApis, } from './MaterialExercise';
3
3
  export { default as MaterialExerciseMobile } from './MaterialExerciseMobile';
4
4
  export type { MaterialExerciseMobileProps } from './MaterialExerciseMobile';
5
- export type { MaterialExerciseCommonApis, MaterialExerciseCommonApiSendTextToTerminal, MaterialExerciseCommonProps, MaterialExerciseCommonPropExerciseImageEmptyOptions, MaterialExerciseCommonPropOnCodeHelpRequest, MaterialExerciseCommonPropOnDefaultExerciseRoomIdSet, MaterialExerciseCommonPropOnExerciseRoomIdChange, MaterialExerciseCommonPropOnRunningDone, MaterialExerciseCommonPropOnSubmit, MaterialExerciseCommonPropOnError, } from './context/types';
5
+ export type { MaterialExerciseCommonApis, MaterialExerciseCommonApiSendTextToTerminal, MaterialExerciseCommonProps, MaterialExerciseCommonPropExerciseImageEmptyOptions, MaterialExerciseCommonPropOnCodeHelpRequest, MaterialExerciseCommonPropOnCodeEditorChange, MaterialExerciseCommonPropOnStdioError, MaterialExerciseCommonPropOnDefaultExerciseRoomIdSet, MaterialExerciseCommonPropOnExerciseRoomIdChange, MaterialExerciseCommonPropOnRunningDone, MaterialExerciseCommonPropOnSubmit, MaterialExerciseCommonPropOnError, } from './context/types';
@@ -59,6 +59,8 @@ export interface MaterialExerciseCommonProps extends MaterialCommponentCommonPro
59
59
  onMultiLangLanguageChange?: MaterialExerciseCommonPropOnMultiLangLanguageChange;
60
60
  onReferenceDocsToggle?: () => void;
61
61
  onCodeHelpRequest?: MaterialExerciseCommonPropOnCodeHelpRequest;
62
+ onCodeEditorChange?: MaterialExerciseCommonPropOnCodeEditorChange;
63
+ onStdioError?: MaterialExerciseCommonPropOnStdioError;
62
64
  onDefaultExerciseRoomIdSet?: MaterialExerciseCommonPropOnDefaultExerciseRoomIdSet;
63
65
  onExerciseRoomIdChange?: MaterialExerciseCommonPropOnExerciseRoomIdChange;
64
66
  onExerciseRunningDone?: MaterialExerciseCommonPropOnRunningDone;
@@ -72,7 +74,7 @@ export type MaterialExerciseCommonPropExerciseImageEmptyOptions = ExerciseProvid
72
74
  /**
73
75
  * On code help button click.
74
76
  */
75
- export type MaterialExerciseCommonPropOnCodeHelpRequest = (params: {
77
+ export type MaterialExerciseCommonPropOnCodeHelpRequest = (payload: {
76
78
  code: string;
77
79
  error?: CodeHelperError;
78
80
  payload?: any;
@@ -80,6 +82,18 @@ export type MaterialExerciseCommonPropOnCodeHelpRequest = (params: {
80
82
  instruction?: string;
81
83
  openFileContent?: string;
82
84
  }) => void;
85
+ /**
86
+ * On code editor drag.
87
+ */
88
+ export type MaterialExerciseCommonPropOnCodeEditorChange = (payload: {
89
+ filename: string;
90
+ content: string;
91
+ selectedContent: string;
92
+ }) => void;
93
+ /**
94
+ * On stdio error.
95
+ */
96
+ export type MaterialExerciseCommonPropOnStdioError = (error: CodeHelperError) => void;
83
97
  /**
84
98
  * On multi language change.
85
99
  */
@@ -1,9 +1,10 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
+ import { useDebounce } from 'react-use';
2
3
  import { getOrgMaterialExerciseExerciseImageExerciseFileGet } from '@elice/api-client';
3
4
  import { Flex } from '@elice/blocks';
4
5
  import { enums } from '@elice/types';
5
6
  import { FlutterApp } from '@elice/utils';
6
- import { useRecoilValue, useSetRecoilState } from 'recoil';
7
+ import { useRecoilValue, useSetRecoilState, useRecoilState } from 'recoil';
7
8
  import ExerciseFileShimmer from '../../shared/exercise-shimmer/ExerciseFileShimmer.js';
8
9
  import '../../shared/exercise-shimmer/ExerciseFileTabsShimmer.js';
9
10
  import '../../shared/exercise-shimmer/ExerciseFileTabShimmer.js';
@@ -13,7 +14,6 @@ import FileViewerNonViewable from '../../shared/file-viewer/FileViewerNonViewabl
13
14
  import MonacoEditorLazy from '../../shared/monaco-editor/MonacoEditorLazy.js';
14
15
  import '../../shared/monaco-editor/MonacoEditorPerferenceForm.js';
15
16
  import 'monaco-editor/esm/vs/editor/editor.api';
16
- import 'react-use';
17
17
  import 'socket.io-client';
18
18
  import { exerciseState, exerciseRoomState, exerciseLectureState, exerciseEditorPreferenceState, exerciseActiveFilenameState, exerciseWebsocketQuery, exerciseFileEditorCursorState, exerciseFileEditorCursorSelectionValueState, exerciseMonacoEditorApisState } from '../context/recoil.js';
19
19
  import { ExerciseContext } from '../context/context.js';
@@ -41,7 +41,8 @@ const ExerciseFileEditor = () => {
41
41
  exerciseRoomId,
42
42
  readOnlyEditor,
43
43
  readOnlyActiveFile,
44
- locale
44
+ locale,
45
+ onCodeEditorChange
45
46
  } = React.useContext(ExerciseContext);
46
47
  const exercise = useRecoilValue(exerciseState(materialExerciseId));
47
48
  const exerciseRoom = useRecoilValue(exerciseRoomState(exerciseRoomId));
@@ -50,8 +51,9 @@ const ExerciseFileEditor = () => {
50
51
  const activeFilename = useRecoilValue(exerciseActiveFilenameState);
51
52
  const readOnly = readOnlyEditor || readOnlyActiveFile;
52
53
  const setUsercodeWebSocketState = useSetRecoilState(exerciseWebsocketQuery('usercodeEdit'));
54
+ const [fileEditorContent, setFileEditorContent] = React.useState(null);
53
55
  const setFileEditorCursorState = useSetRecoilState(exerciseFileEditorCursorState);
54
- const setFileEditorCursorSelectionValueState = useSetRecoilState(exerciseFileEditorCursorSelectionValueState);
56
+ const [fileEditorCursorSelectionValue, setFileEditorCursorSelectionValue] = useRecoilState(exerciseFileEditorCursorSelectionValueState);
55
57
  // editor
56
58
  const editorApis = React.useRef(null);
57
59
  const editorDocHistories = React.useRef({});
@@ -169,6 +171,9 @@ const ExerciseFileEditor = () => {
169
171
  *
170
172
  */
171
173
  const handleChange = e => {
174
+ var _a, _b;
175
+ setFileEditorContent((_b = (_a = editorApis.current) === null || _a === void 0 ? void 0 : _a.getValue()) !== null && _b !== void 0 ? _b : '');
176
+ // === server initiated ===
172
177
  const index = serverInitiatedVersions.current.indexOf(e.versionId - 1);
173
178
  if (index !== -1) {
174
179
  serverInitiatedVersions.current.splice(index, 1);
@@ -206,7 +211,7 @@ const ExerciseFileEditor = () => {
206
211
  wsUsercode.sendCursor(range);
207
212
  }
208
213
  setFileEditorCursorState(range);
209
- setFileEditorCursorSelectionValueState((_b = (_a = editorApis.current) === null || _a === void 0 ? void 0 : _a.getValueInRange(range.from, range.to)) !== null && _b !== void 0 ? _b : '');
214
+ setFileEditorCursorSelectionValue((_b = (_a = editorApis.current) === null || _a === void 0 ? void 0 : _a.getValueInRange(range.from, range.to)) !== null && _b !== void 0 ? _b : '');
210
215
  };
211
216
  /**
212
217
  * Handle editor save shortcut emitted.
@@ -266,6 +271,33 @@ const ExerciseFileEditor = () => {
266
271
  // eslint-disable-next-line react-hooks/exhaustive-deps
267
272
  []);
268
273
  //
274
+ // Emit editor change event to parent component.
275
+ //
276
+ useDebounce(() => {
277
+ if (!isReady) {
278
+ return;
279
+ }
280
+ if (!activeFilename || fileEditorContent === null) {
281
+ return;
282
+ }
283
+ if (typeof onCodeEditorChange === 'function') {
284
+ onCodeEditorChange({
285
+ filename: activeFilename,
286
+ content: fileEditorContent,
287
+ selectedContent: fileEditorCursorSelectionValue !== null && fileEditorCursorSelectionValue !== void 0 ? fileEditorCursorSelectionValue : ''
288
+ });
289
+ }
290
+ }, 150, [isReady, activeFilename, fileEditorContent, fileEditorCursorSelectionValue]);
291
+ //
292
+ // Update file content on active file change.
293
+ //
294
+ useEffect(() => {
295
+ var _a, _b;
296
+ if (activeFilename && isReady) {
297
+ setFileEditorContent((_b = (_a = editorApis.current) === null || _a === void 0 ? void 0 : _a.getValue()) !== null && _b !== void 0 ? _b : '');
298
+ }
299
+ }, [activeFilename, isReady]);
300
+ //
269
301
  //
270
302
  //
271
303
  // empty file
@@ -47,7 +47,8 @@ const ExerciseRunner = ({
47
47
  }) => {
48
48
  var _a, _b, _c;
49
49
  const {
50
- onCodeHelpRequest
50
+ onCodeHelpRequest,
51
+ onStdioError
51
52
  } = React.useContext(ExerciseContext);
52
53
  const {
53
54
  materialExerciseId,
@@ -77,6 +78,9 @@ const ExerciseRunner = ({
77
78
  const handleAiHelpibotError = text => {
78
79
  var _a, _b, _c;
79
80
  const error = getProgrammingErrorResult(text);
81
+ if (!!error && typeof onStdioError === 'function') {
82
+ onStdioError(error);
83
+ }
80
84
  if (!!error && typeof onCodeHelpRequest === 'function') {
81
85
  onCodeHelpRequest({
82
86
  code: error.errorCode,
@@ -2,4 +2,4 @@ export { default as MaterialExercise } from './MaterialExercise';
2
2
  export type { MaterialExerciseProps, MaterialExerciseApis, } from './MaterialExercise';
3
3
  export { default as MaterialExerciseMobile } from './MaterialExerciseMobile';
4
4
  export type { MaterialExerciseMobileProps } from './MaterialExerciseMobile';
5
- export type { MaterialExerciseCommonApis, MaterialExerciseCommonApiSendTextToTerminal, MaterialExerciseCommonProps, MaterialExerciseCommonPropExerciseImageEmptyOptions, MaterialExerciseCommonPropOnCodeHelpRequest, MaterialExerciseCommonPropOnDefaultExerciseRoomIdSet, MaterialExerciseCommonPropOnExerciseRoomIdChange, MaterialExerciseCommonPropOnRunningDone, MaterialExerciseCommonPropOnSubmit, MaterialExerciseCommonPropOnError, } from './context/types';
5
+ export type { MaterialExerciseCommonApis, MaterialExerciseCommonApiSendTextToTerminal, MaterialExerciseCommonProps, MaterialExerciseCommonPropExerciseImageEmptyOptions, MaterialExerciseCommonPropOnCodeHelpRequest, MaterialExerciseCommonPropOnCodeEditorChange, MaterialExerciseCommonPropOnStdioError, MaterialExerciseCommonPropOnDefaultExerciseRoomIdSet, MaterialExerciseCommonPropOnExerciseRoomIdChange, MaterialExerciseCommonPropOnRunningDone, MaterialExerciseCommonPropOnSubmit, MaterialExerciseCommonPropOnError, } from './context/types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elice/material-exercise",
3
- "version": "1.240619.0",
3
+ "version": "1.240620.0-editorevent.0",
4
4
  "description": "User view and editing components of Elice material exercise",
5
5
  "repository": "https://git.elicer.io/elice/frontend/library/elice-material",
6
6
  "license": "UNLICENSED",
@@ -85,8 +85,8 @@
85
85
  "@elice/icons": "^1.230814.0",
86
86
  "@elice/intl": "0.240425.0-rc.2",
87
87
  "@elice/markdown": "^1.220815.0",
88
- "@elice/material-shared-types": "1.240619.0",
89
- "@elice/material-shared-utils": "1.240619.0",
88
+ "@elice/material-shared-types": "1.240620.0-editorevent.0",
89
+ "@elice/material-shared-utils": "1.240620.0-editorevent.0",
90
90
  "@elice/mui-elements": "^5.230825.0",
91
91
  "@elice/mui-system": "^5.230825.0",
92
92
  "@elice/types": "^1.240619.0",
@@ -116,5 +116,5 @@
116
116
  "recoil": "^0.6.1",
117
117
  "styled-components": "^5.2.0"
118
118
  },
119
- "gitHead": "38cf96eba78c95ce503ed36ebd3561c68f90ef8f"
119
+ "gitHead": "d0b5976de6ea3217fe8472427c005fa77ae247fc"
120
120
  }