Kaynağa Gözat

feature: questions list, file add open to new group context menu, remove editor tree panel

Arsekl 3 yıl önce
ebeveyn
işleme
0efed1ccb1

+ 1 - 0
package.json

@@ -26,6 +26,7 @@
     "react-redux": "^8.0.2",
     "react-router-dom": "6",
     "react-scripts": "4.0.3",
+    "react-scrollbars-custom": "^4.0.25",
     "redux": "^4.2.0",
     "redux-logger": "^3.0.6",
     "styled-components": "^5.3.3",

+ 37 - 16
public/mock/2.json

@@ -1,19 +1,40 @@
-{"err":0,
-  "res":{
-    "examId":6629,
-    "fetchAt":1669059664,
-    "startAt":1668441600,
-    "endAt":1669737600,
-    "codeList":[
-      {"questionId":113,
-        "language":"JAVA",
-        "projectName":"113-hanoi",
-        "editable":{
-          "/src/main/java/TowerOfHanoi.java":"public class TowerOfHanoi {\n    public void Hanoi(int n) {}\n}\n"},
-        "uneditable":{
-          "/README.md":"2.实现Hanoi塔\r\n\r\n问题描述:递归实现汉诺塔,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置n个盘子。\r\n要求把A杆上的盘子全部移到B杆上,并仍保持原有顺序叠好。\r\n操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。\r\n\r\n完成函数Hanoi,使用标准输出打印移动盘子的过程。\r\n输入:n为正整数,n >= 1\r\n\r\n示例:\r\nn = 2\r\n打印结果(最后一行有换行符):\r\nMove disk 1 from A to C\r\nMove disk 2 from A to B\r\nMove disk 1 from C to B\r\n"
-        }
+{
+  "err": 0,
+  "res": {
+    "id": 6553,
+    "name": "联调",
+    "startAt": 1667232000,
+    "endAt": 1669737600,
+    "status": "AVAILABLE",
+    "creator": {
+      "id": 271,
+      "username": "kenny67nju"
+    },
+    "joined": false,
+    "courseId": 52,
+    "url": null,
+    "problems": [
+      {
+        "id": 110,
+        "name": "plus2",
+        "type": "JAVA",
+        "description": "plus2"
+      },
+      {
+        "id": 113,
+        "name": "hanoi",
+        "type": "JAVA",
+        "description": "汉诺塔问题"
+      },
+      {
+        "id": 95,
+        "name": "good-map-game-new",
+        "type": "JAVA",
+        "description": "面向对象"
       }
-    ]
+    ],
+    "result": null,
+    "created_at": 1667473399,
+    "course": "基于Java的面向对象编程范式2022"
   }
 }

+ 119 - 5
src/api/index.ts

@@ -1,10 +1,8 @@
 import http from "../common/http";
 import {Gitlab} from "@gitbeaker/browser";
-import {store} from "../redux/store";
-import {loadError, loadSuccess} from "../redux/action/action";
+import {store} from "@/redux/store";
+import {loadError, loadSuccess} from "@/redux/action/action";
 import {IFolderTreeNodeProps} from "@dtinsight/molecule/esm/model";
-import {RepositoryTreeSchema} from "@gitbeaker/core/dist/types/resources/Repositories";
-import {cloneDeep} from "lodash";
 
 const basePath = './mock';
 const apiPath = '/api'
@@ -150,6 +148,53 @@ const api = {
         return http.get(`${apiPath}/record/${recordId}`)
     },
 
+    getExamTest(examId: number):Promise<any> {
+        return new Promise((resolve => {
+            setTimeout(()=>{
+                resolve(JSON.parse("{\n" +
+                    "  \"err\": 0,\n" +
+                    "  \"res\": {\n" +
+                    "    \"id\": 6553,\n" +
+                    "    \"name\": \"联调\",\n" +
+                    "    \"startAt\": 1667232000,\n" +
+                    "    \"endAt\": 1669737600,\n" +
+                    "    \"status\": \"AVAILABLE\",\n" +
+                    "    \"creator\": {\n" +
+                    "      \"id\": 271,\n" +
+                    "      \"username\": \"kenny67nju\"\n" +
+                    "    },\n" +
+                    "    \"joined\": false,\n" +
+                    "    \"courseId\": 52,\n" +
+                    "    \"url\": null,\n" +
+                    "    \"problems\": [\n" +
+                    "      {\n" +
+                    "        \"id\": 110,\n" +
+                    "        \"name\": \"plus2\",\n" +
+                    "        \"type\": \"JAVA\",\n" +
+                    "        \"description\": \"plus2\"\n" +
+                    "      },\n" +
+                    "      {\n" +
+                    "        \"id\": 113,\n" +
+                    "        \"name\": \"hanoi\",\n" +
+                    "        \"type\": \"JAVA\",\n" +
+                    "        \"description\": \"汉诺塔问题\"\n" +
+                    "      },\n" +
+                    "      {\n" +
+                    "        \"id\": 95,\n" +
+                    "        \"name\": \"good-map-game-new\",\n" +
+                    "        \"type\": \"JAVA\",\n" +
+                    "        \"description\": \"面向对象\"\n" +
+                    "      }\n" +
+                    "    ],\n" +
+                    "    \"result\": null,\n" +
+                    "    \"created_at\": 1667473399,\n" +
+                    "    \"course\": \"基于Java的面向对象编程范式2022\"\n" +
+                    "  }\n" +
+                    "}"))
+            }, 500)
+        }))
+    },
+
     getRecordTest(recordId: number | string) {
         return new Promise((resolve) => {
             setTimeout(() => {
@@ -173,7 +218,7 @@ const api = {
                     "    }\n" +
                     "  }\n" +
                     "}"))
-            }, 2500)
+            }, 500)
         })
     },
 
@@ -284,6 +329,7 @@ export interface ExamSerializer {
         id?: number
         username?: string
     }
+    problems?: ProblemSerializer[]
     joined?: boolean
     courseId?: number
     courseName?: string
@@ -292,12 +338,80 @@ export interface ExamSerializer {
     url?: string
 }
 
+export type ProblemType =
+    | 'JAVA' // 普通Java题目,统计测试用例通过情况
+    | 'JAVA_DATABASE' // 带数据库的Java题目,统计测试用例通过情况
+    | 'PYTHON' // 普通Python题目,统计测试用例通过情况
+    | 'JAVA_COVERAGE' // Java测试覆盖率题目,统计测试覆盖率
+    | 'PYTHON_COVERAGE' // Python测试覆盖率题目,统计测试覆盖率
+    | 'JAVA_MUTATION' // Java变异覆盖题目,统计变异覆盖率
+    | 'JAVA_DATA_STRUCTURE' // 带数据结构的Java题目,统计测试用例通过情况
+    | 'JAVA_COMPUTER_ORGANIZATION' // 带计算机组成原理的Java题目,统计测试用例通过情况
+    | 'JAVA_SOFTWARE_DESIGN' // 带软件设计的Java题目,统计测试用例通过情况
+
+export interface ProblemSerializer {
+    id?: number
+    name?: string
+    type?: ProblemType
+    description?: string
+    publicFiles?: Array<string>
+    protectedFiles?: Array<string>
+    privateFiles?: Array<string>
+    createdAt?: number
+    modifiedAt?: number
+    deprecated?: boolean
+    creator?: {
+        id?: number
+        username?: string
+    }
+}
+
 export interface ReceivedData<R = {}, E = 0> {
+    msg?: string
     err?: E | 0 | 401
     res?: R
     page?: PageSerializer
 }
 
+export type UserRole =
+    | 'STUDENT'
+    | 'TEACHER'
+    | 'ADMIN'
+    | 'HR'
+    | 'STAFF'
+    | 'SEEC'
+    | 'SEECX'
+    | 'ASSISTANT'
+
+export interface UserSerializer {
+    id?: number
+    score?: number
+    username?: string
+    email?: string
+    phone?: string
+    role?: UserRole
+}
+
+export interface CodeSerializer {
+    examId?: number
+    fetchAt?: number
+    startAt?: number
+    endAt?: number
+    codeList?: Array<NestedCodeItemVO>
+}
+
+export interface NestedCodeItemVO {
+    questionId?: number
+    language?: string
+    projectName?: string
+    editable?: {
+        [key: string]: string
+    }
+    uneditable?: {
+        [key: string]: string
+    }
+}
+
 export type ExamStatus =
     | 'PREPARING' // 正在准备试题,通常是正在PUSH试题
     | 'UNAVAILABLE' // 考试不可用,通常是PUSH试题失败

+ 108 - 0
src/components/questionListPanel/index.tsx

@@ -0,0 +1,108 @@
+import React, {useEffect, useLayoutEffect, useRef} from "react";
+import {Icon, Scrollable} from "@dtinsight/molecule/esm/components"
+import {
+    editorTreeActiveItemClassName,
+    editorTreeClassName, editorTreeFileIconClassName, editorTreeFileNameClassName,
+    editorTreeItemClassName
+} from "@dtinsight/molecule/esm/workbench/sidebar/explore/base";
+import {CodeSerializer, ProblemSerializer} from "@/api";
+import {store} from "@/redux/store";
+import classNames from "classnames";
+import {getQuestion} from "@/utils";
+import {IStoreState} from "@/redux/reducer/reducer";
+import {connect} from "react-redux";
+import Scrollbar from "react-scrollbars-custom";
+import {HEADER_HEIGTH, MAX_GROW_HEIGHT} from "@dtinsight/molecule/esm/components/collapse";
+
+export interface IQuestionListProps {
+    questions: ProblemSerializer[],
+    currentId: number
+}
+
+const QuestionListView = (props: IQuestionListProps) => {
+    const {
+        questions,
+        currentId
+    } = props
+
+    const wrapper = useRef<HTMLDivElement>(null);
+    const scrollable = useRef<Scrollbar>(null);
+    useLayoutEffect(() => {
+        const scrollHeight = scrollable.current?.scrollHeight || 0;
+        if (scrollHeight > MAX_GROW_HEIGHT - HEADER_HEIGTH) {
+            const activeItem = wrapper.current?.querySelector<HTMLDivElement>(
+                `.${editorTreeActiveItemClassName}`
+            );
+            if (activeItem) {
+                const top = activeItem.offsetTop;
+                scrollable.current?.scrollTo(0, top);
+            }
+        }
+    }, [questions]);
+    useEffect(() => {
+        console.log()
+    })
+    return (
+        <Scrollable noScrollX isShowShadow ref={scrollable}>
+            <div
+                className={editorTreeClassName}
+                ref={wrapper}
+            >
+                {questions.map((question, index) => {
+                    const isActive = currentId ? question.id === currentId : false
+                    return (
+                        <React.Fragment key={index}>
+                            <div
+                                title={
+                                    question.name
+                                }
+                                className={classNames(
+                                    editorTreeItemClassName,
+                                    isActive &&
+                                    editorTreeActiveItemClassName
+                                )}
+                                tabIndex={0}
+                                key={`${index}_${question.id}`}
+                                onClick={
+                                    () => {
+                                        if (!isActive && store.getState().loadQuestion.examId)
+                                            getQuestion(store.getState().loadQuestion.examId as unknown as string, question.id as unknown as string)
+                                    }
+                                }
+                            >
+                                <Icon
+                                    className={
+                                        editorTreeFileIconClassName
+                                    }
+                                    type='file-submodule'
+                                />
+                                <span
+                                    className={
+                                        editorTreeFileNameClassName
+                                    }
+                                >
+                                {question.name}
+                            </span>
+                            </div>
+                        </React.Fragment>
+                    )
+                })}
+            </ div>
+        </Scrollable>
+    )
+}
+
+function getCurrentId(question: CodeSerializer): number {
+    if (question && question.codeList && question.codeList[0] && question.codeList[0].questionId)
+        return question.codeList[0].questionId
+    else return 0
+}
+
+const mapStateToProps = (state: IStoreState): IQuestionListProps => {
+    return {
+        questions: state.loadQuestionList,
+        currentId: getCurrentId(state.loadQuestion)
+    }
+}
+
+export const QuestionList = connect(mapStateToProps)(QuestionListView)

+ 13 - 0
src/constant/index.ts

@@ -1,3 +1,6 @@
+import {IMenuItemProps} from "@dtinsight/molecule/esm/components";
+import {IExplorerPanelItem} from "@dtinsight/molecule/esm/model";
+
 export enum ID_COLLECTIONS{
     COLOR_THEME_ID = 'colorTheme',
     OUTPUT_LOG_ID = 'panel.output.log',
@@ -6,4 +9,14 @@ export enum ID_COLLECTIONS{
 export enum DRAWER_MENU_ENUM {
     RESULT = 'result',
     SUBMIT = 'submit'
+}
+
+export const OPEN_IN_NEW_GROUP: IMenuItemProps = {
+    id: 'explorer.openInNewGroup',
+    name: 'Open In New Group'
+}
+
+export const QUESTION_LIST_PANEL: IExplorerPanelItem = {
+    id: 'explorer.questionList',
+    name: 'QUESTION LIST'
 }

+ 35 - 7
src/extensions/init.tsx

@@ -1,6 +1,6 @@
 import molecule from "@dtinsight/molecule";
-import {ColorThemeMode, IActivityMenuItemProps} from "@dtinsight/molecule/esm/model";
-import {DRAWER_MENU_ENUM, ID_COLLECTIONS} from "../constant";
+import {ColorThemeMode, IActivityMenuItemProps, IEditorGroup} from "@dtinsight/molecule/esm/model";
+import {DRAWER_MENU_ENUM, ID_COLLECTIONS, OPEN_IN_NEW_GROUP, QUESTION_LIST_PANEL} from "../constant";
 import {UniqueId} from "@dtinsight/molecule/esm/common/types";
 import {IExtension} from "@dtinsight/molecule/esm/model/extension";
 import LogPane from "@/components/logPane";
@@ -9,12 +9,14 @@ import {jumpToLogin} from "@/services/auth";
 import Api from "@/api";
 import {history} from "umi";
 import notification from "@/components/notification";
-import {deleteCookie, getQuestion, goToLogin, updateAccountContext} from "@/utils";
+import {deleteCookie, getQuestion, goToLogin, randomId, updateAccountContext} from "@/utils";
 import {loadUser} from "@/redux/action/action";
 import {QuickTogglePanelAction} from "@dtinsight/molecule/esm/monaco/quickTogglePanelAction";
 import {Button} from "antd";
 import {jumpToCoder} from "@/services/exam";
 import classNames from "classnames";
+import {transformToEditorTab} from "@/common";
+import {QuestionList} from "@/components/questionListPanel";
 
 function loadStyles(url: string) {
     const link = document.createElement('link');
@@ -109,11 +111,17 @@ function initMenuBar() {
 }
 
 function initExpandCollapse() {
-    const { SAMPLE_FOLDER_PANEL_ID, EDITOR_PANEL_ID } = molecule.builtin.getConstants();
-    molecule.explorer.setExpandedPanels([EDITOR_PANEL_ID!, SAMPLE_FOLDER_PANEL_ID!]);
+    const { SAMPLE_FOLDER_PANEL_ID } = molecule.builtin.getConstants();
+    molecule.explorer.setExpandedPanels([SAMPLE_FOLDER_PANEL_ID!, QUESTION_LIST_PANEL.id]);
 }
 
 function initExplorer() {
+    molecule.explorer.addPanel({
+        ...QUESTION_LIST_PANEL,
+        renderPanel:() => (
+            <QuestionList store={store}/>
+        ),
+    })
     const explorerData = molecule.explorer.getState().data?.concat() || [];
     const { SAMPLE_FOLDER_PANEL_ID, EDITOR_PANEL_ID } = molecule.builtin.getConstants();
     const folderTreePane = explorerData.find((item) => item.id === SAMPLE_FOLDER_PANEL_ID);
@@ -142,12 +150,32 @@ function initExplorer() {
     molecule.editorTree.onCloseSaved((groupId) => {
         molecule.editor.closeAll(groupId);
     });
-    molecule.folderTree.setFileContextMenu([])
+    molecule.folderTree.setFileContextMenu([OPEN_IN_NEW_GROUP])
+    molecule.folderTree.onContextMenu((contextMenu, treeNode) => {
+        switch (contextMenu.id) {
+            case OPEN_IN_NEW_GROUP.id: {
+                const { groups = [] } = molecule.editor.getState();
+                const id = randomId();
+                const initialTab = Object.assign({}, transformToEditorTab(treeNode!));
+                const newGroup: IEditorGroup = Object.assign({})
+                newGroup.data = [initialTab];
+                newGroup.tab = initialTab;
+                newGroup.activeTab = initialTab.id;
+                newGroup.id = id;
+                molecule.editor.setState({
+                    current: newGroup,
+                    groups: [...groups, newGroup]
+                })
+                break;
+            }
+            default:
+                break;
+        }
+    })
     molecule.folderTree.setFolderContextMenu([])
     const folderTreeState = molecule.folderTree.getState()
     const folderTree = folderTreeState?.folderTree
     if (folderTree && folderTreeState && folderTree.contextMenu) folderTree.contextMenu = []
-    console.log(molecule.folderTree.getState())
 }
 
 function initializeEntry() {

+ 7 - 1
src/pages/index.tsx

@@ -8,14 +8,17 @@ import {history} from "umi";
 import Layout from "@/layout";
 import {useEffect, useLayoutEffect} from "react";
 import notification from "@/components/notification";
-import {getCookie, getQuestion, goToLogin, setCookie} from "@/utils";
+import {getCookie, getQuestion, getQuestionList, goToLogin, setCookie} from "@/utils";
 import Result from "@/pages/seec/result";
+import {IExplorerPanelItem} from "@dtinsight/molecule/esm/model";
+import {IActionBarItemProps} from "@dtinsight/molecule/esm/components";
 
 const moInstance = create({
     extensions,
 });
 
 moInstance.onBeforeInit(() => {
+    molecule.builtin.inactiveModule('builtInExplorerEditorPanel')
     molecule.builtin.inactiveModule('builtInExplorerOutlinePanel')
     molecule.builtin.inactiveModule('builtInOutputPanel');
     molecule.builtin.inactiveModule('builtInPanelProblems')
@@ -58,11 +61,13 @@ export default function HomePage() {
         if (history.location.query?.id_token) {
             setCookie('token', history.location.query?.id_token as string)
             if (history.location.query?.examId && history.location.query?.questionId) {
+                getQuestionList(history.location.query?.examId as unknown as number)
                 getQuestion(history.location.query?.examId as string, history.location.query?.questionId as string)
             }
             history.push('/')
         }
         else if (history.location.query?.examId && history.location.query?.questionId) {
+            getQuestionList(history.location.query?.examId as unknown as number)
             getQuestion(history.location.query?.examId as string, history.location.query?.questionId as string)
             history.push('/')
         }
@@ -73,6 +78,7 @@ export default function HomePage() {
             if (history.location.query?.id_token) {
                 setCookie('token', history.location.query?.id_token as string)
                 if (history.location.query?.examId && history.location.query?.questionId) {
+                    getQuestionList(history.location.query?.examId as unknown as number)
                     getQuestion(history.location.query?.examId as string, history.location.query?.questionId as string)
                 }
                 history.push('/')

+ 11 - 39
src/redux/action/action.ts

@@ -1,3 +1,5 @@
+import {CodeSerializer, ProblemSerializer, UserSerializer} from "@/api";
+
 export const LOADING_PROJECT = "LOADING_PROJECT"
 
 export const LOAD_SUCCESS = "LOAD_SUCCESS"
@@ -6,46 +8,9 @@ export const LOAD_ERROR = "LOAD_ERROR"
 
 export const LOAD_USER = "LOAD_USER"
 
-export const LOAD_QUESTION = "LOAD_QUESTION"
-
-export type UserRole =
-    | 'STUDENT'
-    | 'TEACHER'
-    | 'ADMIN'
-    | 'HR'
-    | 'STAFF'
-    | 'SEEC'
-    | 'SEECX'
-    | 'ASSISTANT'
-
-export interface UserSerializer {
-    id?: number
-    score?: number
-    username?: string
-    email?: string
-    phone?: string
-    role?: UserRole
-}
-
-export interface CodeSerializer {
-    examId?: number
-    fetchAt?: number
-    startAt?: number
-    endAt?: number
-    codeList?: Array<NestedCodeItemVO>
-}
+export const LOAD_QUESTION_LIST = "LOAD_QUESTION_LIST"
 
-export interface NestedCodeItemVO {
-    questionId?: number
-    language?: string
-    projectName?: string
-    editable?: {
-        [key: string]: string
-    }
-    uneditable?: {
-        [key: string]: string
-    }
-}
+export const LOAD_QUESTION = "LOAD_QUESTION"
 
 export function loadingProject(projectInfo: {
     nameScope: string,
@@ -78,6 +43,13 @@ export function loadUser(userInfo: UserSerializer) {
     }
 }
 
+export function loadQuestionList(questionList: ProblemSerializer[]) {
+    return {
+        type: LOAD_QUESTION_LIST,
+        questionList
+    }
+}
+
 export function loadQuestion(questionInfo: CodeSerializer) {
     return {
         type: LOAD_QUESTION,

+ 23 - 4
src/redux/reducer/reducer.ts

@@ -1,13 +1,12 @@
 import { combineReducers } from 'redux'
 import {
-    CodeSerializer,
     LOAD_ERROR,
-    LOAD_QUESTION,
+    LOAD_QUESTION, LOAD_QUESTION_LIST,
     LOAD_SUCCESS,
     LOAD_USER,
     LOADING_PROJECT,
-    UserSerializer
 } from "../action/action";
+import {CodeSerializer, ProblemSerializer, UserSerializer} from "@/api";
 export type IState = {
     isLoading: boolean,
     isValid: boolean,
@@ -66,6 +65,20 @@ function loadUser(state: UserSerializer = {}, action: {
     }
 }
 
+function loadQuestionList(state: ProblemSerializer[] = [], action: {
+    type: string,
+    questionList: ProblemSerializer[]
+}) {
+    switch (action.type) {
+        case LOAD_QUESTION_LIST:
+            return Object.assign([], state, [
+                ...action.questionList
+            ])
+        default:
+            return state
+    }
+}
+
 function loadQuestion(state: CodeSerializer = {}, action: {
     type: string,
     questionInfo: CodeSerializer
@@ -83,8 +96,14 @@ function loadQuestion(state: CodeSerializer = {}, action: {
 const rootReducer = combineReducers({
         loadProject,
         loadUser,
-        loadQuestion
+        loadQuestion,
+        loadQuestionList
     }
 )
 
+export interface IStoreState {
+    loadUser: UserSerializer,
+    loadQuestion: CodeSerializer,
+    loadQuestionList:  ProblemSerializer[]
+}
 export default rootReducer

+ 33 - 3
src/utils/index.ts

@@ -1,6 +1,6 @@
-import Api from "@/api";
+import Api, {ExamSerializer, ReceivedData} from "@/api";
 import {store} from "@/redux/store";
-import {loadQuestion, loadUser} from "@/redux/action/action";
+import {loadQuestion, loadQuestionList, loadUser} from "@/redux/action/action";
 import {history} from "@@/core/history";
 import notification from "@/components/notification";
 import {IActivityMenuItemProps, IFolderTreeNodeProps} from "@dtinsight/molecule/esm/model";
@@ -9,6 +9,10 @@ import {jumpToLogin} from "@/services/auth";
 import ex from "umi/dist";
 import {message} from "antd";
 
+export function randomId() {
+    return Date.now() + Math.round(Math.random() * 1000);
+}
+
 export function setCookie(name: string, value: string) {
     const expires = new Date();
     expires.setTime(expires.getTime() + 24 * 60 * 60 * 1000);
@@ -60,7 +64,7 @@ export function goToLogin() {
                     {
                         id: 'logout',
                         icon: 'log-out',
-                        name: 'log out',
+                        name: 'Log Out',
                         onClick: () => {
                             store.dispatch(loadUser({}))
                             deleteCookie('token')
@@ -81,6 +85,30 @@ export function goToLogin() {
     }
 }
 
+export function getQuestionList(examId: number) {
+    if (examId === store.getState().loadQuestion.examId) {
+        console.log(examId)
+        return store.getState().loadQuestionList
+    } else {
+        Api.getByExamId(examId).then((res: ReceivedData<ExamSerializer>) => {
+            if (res.err === 0) {
+                const exam = res.res
+                if (exam && exam.problems) {
+                    store.dispatch(loadQuestionList(exam.problems))
+                    return res
+                }
+            }
+            else {
+                notification.error({key: 'QuestionError', message: res.msg? res.msg: 'Get QuestionsList Error'})
+            }
+        }).catch(reason => {
+                console.log(reason)
+                notification.error({key: 'QuestionError', message: reason})
+            }
+        )
+    }
+}
+
 const extToLang: { [prop: string]: string } = {
     'html': 'html',
     'js': 'javascript',
@@ -93,6 +121,7 @@ const extToLang: { [prop: string]: string } = {
     'md': 'markdown'
 }
 let id = 1
+
 function generateFileNodes(
     nodes: IFolderTreeNodeProps[],
     pathList: string[],
@@ -147,6 +176,7 @@ function generateFileNodes(
         ]
     }
 }
+
 export function getQuestion(examId: string, questionId: string) {
     const hide = message.loading("Loading...", 0)
     const regPos = /^[0-9]+/;