diff --git a/src/actions/workspaces.ts b/src/actions/workspaces.ts index 11b3afd629..c8d8d95b8f 100644 --- a/src/actions/workspaces.ts +++ b/src/actions/workspaces.ts @@ -1,6 +1,6 @@ import { ActionCreator } from 'redux' -import { ExternalLibraryName } from '../components/assessment/assessmentShape' +import { Library } from '../components/assessment/assessmentShape' import { IWorkspaceState } from '../reducers/states' import * as actionTypes from './actionTypes' @@ -86,7 +86,7 @@ export const playgroundExternalSelect: ActionCreator = ( ) => ({ type: actionTypes.PLAYGROUND_EXTERNAL_SELECT, payload: { - external: external.displayName, + externalLibraryName: external.name, workspaceLocation } }) @@ -94,22 +94,15 @@ export const playgroundExternalSelect: ActionCreator = ( /** * Clears the js-slang Context at a specified workspace location. * - * @param chapter the SICP chapter for the context to be set in - * @param externals a list of symbols to be exposed from the global scope - * @param externalLibraryName the name of the external library used + * @param library the Library that the context shall be using * @param workspaceLocation the location of the workspace + * + * @see Library in assessmentShape.ts */ -export const clearContext = ( - chapter: number, - externals: string[], - externalLibraryName: ExternalLibraryName, - workspaceLocation: WorkspaceLocation -) => ({ +export const clearContext = (library: Library, workspaceLocation: WorkspaceLocation) => ({ type: actionTypes.CLEAR_CONTEXT, payload: { - chapter, - externals, - externalLibraryName, + library, workspaceLocation } }) diff --git a/src/components/Application.tsx b/src/components/Application.tsx index 3188549023..e8a4cec52a 100644 --- a/src/components/Application.tsx +++ b/src/components/Application.tsx @@ -19,11 +19,11 @@ export interface IStateProps { role?: Role username?: string currentPlaygroundChapter: number - currentPlaygroundExternals: string[] + currentPlaygroundExternalSymbols: string[] } export interface IDispatchProps { - handleClearContext: (chapter: number, externals: string[]) => void + handleClearContext: (chapter: number, symbols: string[]) => void handleEditorValueChange: (val: string) => void } @@ -70,9 +70,9 @@ const parsePlayground = (props: IApplicationProps) => { if (prgrm) { props.handleEditorValueChange(prgrm) } - /** Changes the chapter, retains the externals. */ + /** Changes the chapter, retains the external symbols. */ if (lib) { - props.handleClearContext(lib, props.currentPlaygroundExternals) + props.handleClearContext(lib, props.currentPlaygroundExternalSymbols) } } diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index 87849c0613..7a858b49f3 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -20,7 +20,7 @@ export interface IStateProps { replValue: string sideContentHeight?: number sourceChapter: number - externalLibrary: string + externalLibraryName: string } export interface IDispatchProps { @@ -58,7 +58,7 @@ class Playground extends React.Component { public render() { const workspaceProps: WorkspaceProps = { controlBarProps: { - externalLibrary: this.props.externalLibrary, + externalLibraryName: this.props.externalLibraryName, handleChapterSelect: this.props.handleChapterSelect, handleExternalSelect: this.props.handleExternalSelect, handleEditorEval: this.props.handleEditorEval, diff --git a/src/components/__tests__/Application.tsx b/src/components/__tests__/Application.tsx index 34f1d72bf9..5ad8b5b96b 100644 --- a/src/components/__tests__/Application.tsx +++ b/src/components/__tests__/Application.tsx @@ -9,8 +9,8 @@ test('Application renders correctly', () => { ...mockRouterProps('/academy', {}), title: 'Cadet', currentPlaygroundChapter: 2, - currentPlaygroundExternals: [], - handleClearContext: (chapter: number, externals: string[]) => {}, + currentPlaygroundExternalSymbols: [], + handleClearContext: (chapter: number, externalSymbols: string[]) => {}, handleEditorValueChange: (val: string) => {} } const app = diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 9101c86aa8..bd7ef823ad 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -2,6 +2,7 @@ import { shallow } from 'enzyme' import * as React from 'react' import { mockRouterProps } from '../../mocks/components' +import { ExternalLibraryNames } from '../assessment/assessmentShape' import Playground, { IPlaygroundProps } from '../Playground' const baseProps = { @@ -11,8 +12,7 @@ const baseProps = { editorWidth: '50%', sideContentHeight: 40, sourceChapter: 2, - /** TODO use constant value */ - externalLibrary: 'none', + externalLibraryName: ExternalLibraryNames.NONE, output: [], replValue: '', handleBrowseHistoryDown: () => {}, diff --git a/src/components/academy/grading/GradingWorkspace.tsx b/src/components/academy/grading/GradingWorkspace.tsx index 2bdffac0dc..b0424db60f 100644 --- a/src/components/academy/grading/GradingWorkspace.tsx +++ b/src/components/academy/grading/GradingWorkspace.tsx @@ -2,7 +2,6 @@ import { NonIdealState, Spinner, Text } from '@blueprintjs/core' import { IconNames } from '@blueprintjs/icons' import * as React from 'react' -import { ExternalLibraryName } from '../../../components/assessment/assessmentShape' import GradingEditor from '../../../containers/academy/grading/GradingEditorContainer' import { InterpreterOutput, IWorkspaceState } from '../../../reducers/states' import { history } from '../../../utils/history' @@ -10,6 +9,7 @@ import { IMCQQuestion, IProgrammingQuestion, IQuestion, + Library, QuestionTypes } from '../../assessment/assessmentShape' import Workspace, { WorkspaceProps } from '../../workspace' @@ -42,11 +42,7 @@ export type DispatchProps = { handleBrowseHistoryUp: () => void handleChangeActiveTab: (activeTab: number) => void handleChapterSelect: (chapter: any, changeEvent: any) => void - handleClearContext: ( - chapter: number, - externals: string[], - externalLibraryName: ExternalLibraryName - ) => void + handleClearContext: (library: Library) => void handleEditorEval: () => void handleEditorValueChange: (val: string) => void handleEditorWidthChange: (widthChange: number) => void @@ -62,19 +58,16 @@ export type DispatchProps = { class GradingWorkspace extends React.Component { /** - * First, check for a need to reset the workspace, - * then fetch the grading. This works because a change in - * submissionId or questionId results in a navigation, causing - * this component to be mounted again. The handleGradingFetch - * occurs after the call to checkWorkspaceReset finishes. + * After mounting (either an older copy of the grading + * or a loading screen), try to fetch a newer grading. */ public componentDidMount() { this.props.handleGradingFetch(this.props.submissionId) } /** - * After the Grading is fetched, there is a check for wether the - * workspace needs to be udpated (a change in submissionId or questionId) + * Once there is an update (due to the grading being fetched), check + * if a workspace reset is needed. */ public componentDidUpdate() { this.checkWorkspaceReset(this.props) @@ -158,9 +151,6 @@ class GradingWorkspace extends React.Component { this.props.storedQuestionId !== questionId ) { const question = this.props.grading[questionId].question as IQuestion - const chapter = question.library.chapter - const externalName = question.library.externalLibraryName - const externals = question.library.externals const editorValue = question.type === QuestionTypes.programming ? question.answer !== null @@ -169,7 +159,7 @@ class GradingWorkspace extends React.Component { : null this.props.handleUpdateCurrentSubmissionId(submissionId, questionId) this.props.handleResetWorkspace({ editorValue }) - this.props.handleClearContext(chapter, externals, externalName) + this.props.handleClearContext(question.library) } } diff --git a/src/components/assessment/AssessmentWorkspace.tsx b/src/components/assessment/AssessmentWorkspace.tsx index 5bf215874f..a38a931210 100644 --- a/src/components/assessment/AssessmentWorkspace.tsx +++ b/src/components/assessment/AssessmentWorkspace.tsx @@ -10,11 +10,11 @@ import Workspace, { WorkspaceProps } from '../workspace' import { ControlBarProps } from '../workspace/ControlBar' import { SideContentProps } from '../workspace/side-content' import { - ExternalLibraryName, IAssessment, IMCQQuestion, IProgrammingQuestion, IQuestion, + Library, QuestionTypes } from './assessmentShape' @@ -45,11 +45,7 @@ export type DispatchProps = { handleBrowseHistoryUp: () => void handleChangeActiveTab: (activeTab: number) => void handleChapterSelect: (chapter: any, changeEvent: any) => void - handleClearContext: ( - chapter: number, - externals: string[], - externalLibraryName: ExternalLibraryName - ) => void + handleClearContext: (library: Library) => void handleEditorEval: () => void handleEditorValueChange: (val: string) => void handleEditorWidthChange: (widthChange: number) => void @@ -70,11 +66,9 @@ class AssessmentWorkspace extends React.Component< public state = { showOverlay: false } /** - * First, check for a need to reset the workspace, - * then fetch the assessment. This works because a change in - * assessmentId or questionId results in a navigation, causing - * this component to be mounted again. The handleAssessmentFetch - * occurs after the call to checkWorkspaceReset finishes. + * After mounting (either an older copy of the assessment + * or a loading screen), try to fetch a newer assessment, + * and show the briefing. */ public componentDidMount() { this.props.handleAssessmentFetch(this.props.assessmentId) @@ -83,6 +77,10 @@ class AssessmentWorkspace extends React.Component< } } + /** + * Once there is an update (due to the assessment being fetched), check + * if a workspace reset is needed. + */ public componentDidUpdate() { this.checkWorkspaceReset(this.props) } @@ -178,9 +176,6 @@ class AssessmentWorkspace extends React.Component< this.props.storedQuestionId !== questionId ) { const question = this.props.assessment.questions[questionId] - const chapter = question.library.chapter - const externalName = question.library.externalLibraryName - const externals = question.library.externals const editorValue = question.type === QuestionTypes.programming ? question.answer !== null @@ -189,7 +184,7 @@ class AssessmentWorkspace extends React.Component< : null this.props.handleUpdateCurrentAssessmentId(assessmentId, questionId) this.props.handleResetWorkspace({ editorValue }) - this.props.handleClearContext(chapter, externals, externalName) + this.props.handleClearContext(question.library) } } diff --git a/src/components/assessment/__tests__/AssessmentWorkspace.tsx b/src/components/assessment/__tests__/AssessmentWorkspace.tsx index 62ef3275a4..1efeffc669 100644 --- a/src/components/assessment/__tests__/AssessmentWorkspace.tsx +++ b/src/components/assessment/__tests__/AssessmentWorkspace.tsx @@ -2,6 +2,7 @@ import { shallow } from 'enzyme' import * as React from 'react' import { mockAssessments } from '../../../mocks/assessmentAPI' +import { Library } from '../assessmentShape' import AssessmentWorkspace, { AssessmentWorkspaceProps } from '../AssessmentWorkspace' const defaultProps: AssessmentWorkspaceProps = { @@ -15,7 +16,7 @@ const defaultProps: AssessmentWorkspaceProps = { handleBrowseHistoryUp: () => {}, handleChangeActiveTab: (activeTab: number) => {}, handleChapterSelect: (chapter: any, changeEvent: any) => {}, - handleClearContext: (chapter: number, externals: string[]) => {}, + handleClearContext: (library: Library) => {}, handleEditorEval: () => {}, handleEditorValueChange: (val: string) => {}, handleEditorWidthChange: (widthChange: number) => {}, diff --git a/src/components/assessment/assessmentShape.ts b/src/components/assessment/assessmentShape.ts index 22bcd163df..c0fca1a82e 100644 --- a/src/components/assessment/assessmentShape.ts +++ b/src/components/assessment/assessmentShape.ts @@ -77,10 +77,13 @@ export enum ExternalLibraryNames { export type ExternalLibraryName = keyof typeof ExternalLibraryNames +type ExternalLibrary = { + name: ExternalLibraryName + symbols: string[] +} + export type Library = { chapter: number - externalLibraryName: ExternalLibraryName - files: string[] - externals: string[] - globals: string[] + external: ExternalLibrary + globals: Array<[string, any]> } diff --git a/src/components/workspace/ControlBar.tsx b/src/components/workspace/ControlBar.tsx index 0b8b2b0ac8..0760d6cf29 100644 --- a/src/components/workspace/ControlBar.tsx +++ b/src/components/workspace/ControlBar.tsx @@ -18,7 +18,7 @@ export type ControlBarProps = { isRunning: boolean queryString?: string sourceChapter: number - externalLibrary?: string + externalLibraryName?: string handleChapterSelect?: (i: IChapter, e: React.ChangeEvent) => void handleExternalSelect?: (i: IExternal, e: React.ChangeEvent) => void handleEditorEval: () => void @@ -44,8 +44,8 @@ interface IChapter { */ interface IExternal { key: number - displayName: string - externals: string[] + name: string + symbols: string[] } class ControlBar extends React.PureComponent { @@ -120,8 +120,8 @@ class ControlBar extends React.PureComponent { ? chapterSelect(this.props.sourceChapter, this.props.handleChapterSelect) : undefined const externalSelectButton = - this.props.hasChapterSelect && this.props.externalLibrary !== undefined - ? externalSelect(this.props.externalLibrary, this.props.handleExternalSelect) + this.props.hasChapterSelect && this.props.externalLibraryName !== undefined + ? externalSelect(this.props.externalLibraryName, this.props.handleExternalSelect!) : undefined return (
@@ -202,19 +202,19 @@ const chapterRenderer: ItemRenderer = (chap, { handleClick, modifiers, ) -const externals = Array.from(externalLibraries.entries()).map((entry, index) => ({ - displayName: entry[0], +const iExternals = Array.from(externalLibraries.entries()).map((entry, index) => ({ + name: entry[0], key: index, - externals: entry[1] + symbols: entry[1] })) const externalSelect = ( currentExternal: string, - handleSelect = (i: IExternal, e: React.ChangeEvent) => {} + handleSelect: (i: IExternal, e: React.ChangeEvent) => void ) => ( () const externalRenderer: ItemRenderer = (external, { handleClick, modifiers, query }) => ( - + ) export default ControlBar diff --git a/src/containers/ApplicationContainer.ts b/src/containers/ApplicationContainer.ts index f7ba4058e1..f75d54b2b4 100644 --- a/src/containers/ApplicationContainer.ts +++ b/src/containers/ApplicationContainer.ts @@ -21,7 +21,7 @@ const mapStateToProps: MapStateToProps = state => ({ role: state.session.role, username: state.session.username, currentPlaygroundChapter: state.workspaces.playground.context.chapter, - currentPlaygroundExternals: state.workspaces.playground.externals + currentPlaygroundExternalSymbols: state.workspaces.playground.externalSymbols }) const workspaceLocation = WorkspaceLocations.playground @@ -29,8 +29,22 @@ const workspaceLocation = WorkspaceLocations.playground const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => bindActionCreators( { - handleClearContext: (chapter: number, externals: string[]) => - clearContext(chapter, externals, ExternalLibraryNames.NONE, workspaceLocation), + /** + * Note that an empty globals is passed (as this is never used in URLs) + * and that ExternalLibraryNames.NONE is used (as URL library support is not ready yet). + */ + handleClearContext: (chapter: number, symbols: string[]) => + clearContext( + { + chapter, + external: { + name: ExternalLibraryNames.NONE, + symbols + }, + globals: [] + }, + workspaceLocation + ), handleEditorValueChange: (val: string) => updateEditorValue(val, workspaceLocation) }, dispatch diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index d17de3aa24..8824fd9654 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -32,7 +32,7 @@ const mapStateToProps: MapStateToProps = state => ({ replValue: state.workspaces.playground.replValue, sideContentHeight: state.workspaces.playground.sideContentHeight, sourceChapter: state.workspaces.playground.context.chapter, - externalLibrary: state.workspaces.playground.playgroundExternal + externalLibraryName: state.workspaces.playground.playgroundExternal }) const location: WorkspaceLocation = 'playground' diff --git a/src/containers/academy/grading/GradingWorkspaceContainer.ts b/src/containers/academy/grading/GradingWorkspaceContainer.ts index a5e0883dbc..b65c573cc1 100644 --- a/src/containers/academy/grading/GradingWorkspaceContainer.ts +++ b/src/containers/academy/grading/GradingWorkspaceContainer.ts @@ -27,7 +27,7 @@ import GradingWorkspace, { OwnProps, StateProps } from '../../../components/academy/grading/GradingWorkspace' -import { ExternalLibraryName } from '../../../components/assessment/assessmentShape' +import { Library } from '../../../components/assessment/assessmentShape' import { IState, IWorkspaceState } from '../../../reducers/states' const workspaceLocation: WorkspaceLocation = 'grading' @@ -55,11 +55,7 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dis handleChangeActiveTab: (activeTab: number) => changeActiveTab(activeTab, workspaceLocation), handleChapterSelect: (chapter: any, changeEvent: any) => chapterSelect(chapter, changeEvent, workspaceLocation), - handleClearContext: ( - chapter: number, - externals: string[], - externalLibraryName: ExternalLibraryName - ) => clearContext(chapter, externals, externalLibraryName, workspaceLocation), + handleClearContext: (library: Library) => clearContext(library, workspaceLocation), handleEditorEval: () => evalEditor(workspaceLocation), handleEditorValueChange: (val: string) => updateEditorValue(val, workspaceLocation), handleEditorWidthChange: (widthChange: number) => diff --git a/src/containers/assessment/AssessmentWorkspaceContainer.ts b/src/containers/assessment/AssessmentWorkspaceContainer.ts index 6c774ac6fb..96ff67e6e1 100644 --- a/src/containers/assessment/AssessmentWorkspaceContainer.ts +++ b/src/containers/assessment/AssessmentWorkspaceContainer.ts @@ -23,7 +23,7 @@ import { updateCurrentAssessmentId, WorkspaceLocation } from '../../actions/workspaces' -import { ExternalLibraryName } from '../../components/assessment/assessmentShape' +import { Library } from '../../components/assessment/assessmentShape' import AssessmentWorkspace, { DispatchProps, OwnProps, @@ -57,11 +57,7 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dis handleChangeActiveTab: (activeTab: number) => changeActiveTab(activeTab, workspaceLocation), handleChapterSelect: (chapter: any, changeEvent: any) => chapterSelect(chapter, changeEvent, workspaceLocation), - handleClearContext: ( - chapter: number, - externals: string[], - externalLibraryName: ExternalLibraryName - ) => clearContext(chapter, externals, externalLibraryName, workspaceLocation), + handleClearContext: (library: Library) => clearContext(library, workspaceLocation), handleEditorEval: () => evalEditor(workspaceLocation), handleEditorValueChange: (val: string) => updateEditorValue(val, workspaceLocation), handleEditorWidthChange: (widthChange: number) => diff --git a/src/mocks/assessmentAPI.ts b/src/mocks/assessmentAPI.ts index 5e21a53bc5..dcb79c7205 100644 --- a/src/mocks/assessmentAPI.ts +++ b/src/mocks/assessmentAPI.ts @@ -80,36 +80,51 @@ export const mockAssessmentOverviews = [ ...mockClosedAssessmentOverviews ] +const mockGlobals: Array<[string, any]> = [ + ['testNumber', 3.141592653589793], + ['testString', 'who dat boi'], + ['testBooleanTrue', true], + ['testBooleanFalse', false], + ['testBooleanUndefined', undefined], + ['testBooleanNull', null], + ['testObject', { a: 1, b: 2 }], + ['testArray', [1, 2, 'a', 'b']] +] + const mockSoundLibrary: Library = { chapter: 1, - externalLibraryName: ExternalLibraryNames.SOUND, - externals: externalLibraries.get(ExternalLibraryNames.SOUND)!, - files: ['mockLibraryFile'], - globals: [] + external: { + name: ExternalLibraryNames.SOUND, + symbols: externalLibraries.get(ExternalLibraryNames.SOUND)! + }, + globals: mockGlobals } export const mock2DRuneLibrary: Library = { chapter: 1, - externalLibraryName: ExternalLibraryNames.TWO_DIM_RUNES, - externals: externalLibraries.get(ExternalLibraryNames.TWO_DIM_RUNES)!, - files: ['mockLibraryFile'], - globals: [] + external: { + name: ExternalLibraryNames.TWO_DIM_RUNES, + symbols: externalLibraries.get(ExternalLibraryNames.TWO_DIM_RUNES)! + }, + globals: mockGlobals } const mock3DRuneLibrary: Library = { chapter: 1, - externalLibraryName: ExternalLibraryNames.THREE_DIM_RUNES, - externals: externalLibraries.get(ExternalLibraryNames.THREE_DIM_RUNES)!, - files: ['mockLibraryFile'], - globals: [] + external: { + name: ExternalLibraryNames.THREE_DIM_RUNES, + symbols: externalLibraries.get(ExternalLibraryNames.THREE_DIM_RUNES)! + }, + globals: mockGlobals } const mockCurveLibrary: Library = { chapter: 1, - externalLibraryName: ExternalLibraryNames.CURVES, - externals: externalLibraries.get(ExternalLibraryNames.CURVES)!, - files: ['mockLibraryFile'], - globals: [] + external: { + name: ExternalLibraryNames.CURVES, + symbols: externalLibraries.get(ExternalLibraryNames.CURVES)! + }, + globals: mockGlobals } export const mockAssessmentQuestions: Array = [ diff --git a/src/reducers/externalLibraries.ts b/src/reducers/externalLibraries.ts index 0cfcfac661..224d1eb381 100644 --- a/src/reducers/externalLibraries.ts +++ b/src/reducers/externalLibraries.ts @@ -1,7 +1,7 @@ import { ExternalLibraryName, ExternalLibraryNames } from '../components/assessment/assessmentShape' /** - * Defines all the externals for playground, i.e full access to runes functionality. + * Defines all the external symbols for playground, i.e full access to runes functionality. */ const TwoDRunesExternals = [ 'show', @@ -47,7 +47,7 @@ const TwoDRunesExternals = [ /** * Defines which external libraries are available for usage, and what - * externals (exposed functions) are under them. + * external symbols (exposed functions) are under them. */ const libEntries: Array<[ExternalLibraryName, string[]]> = [ [ExternalLibraryNames.NONE, []], diff --git a/src/reducers/states.ts b/src/reducers/states.ts index ea9521173b..7ac6c44411 100644 --- a/src/reducers/states.ts +++ b/src/reducers/states.ts @@ -65,7 +65,8 @@ export interface IWorkspaceState { readonly replValue: string readonly sideContentActiveTab: number readonly sideContentHeight?: number - readonly externals: string[] + readonly externalSymbols: string[] + readonly globals: Array<[string, any]> } export interface ISessionState { @@ -195,7 +196,8 @@ export const createDefaultWorkspace = (location: WorkspaceLocation): IWorkspaceS }, replValue: '', sideContentActiveTab: 0, - externals: [] + externalSymbols: [], + globals: [] }) export const defaultComments = 'Comments **here**. Use `markdown` if you ~~are cool~~ want!' diff --git a/src/reducers/workspaces.ts b/src/reducers/workspaces.ts index 53aa824c94..d943c116f5 100644 --- a/src/reducers/workspaces.ts +++ b/src/reducers/workspaces.ts @@ -186,11 +186,12 @@ export const reducer: Reducer = ( [location]: { ...state[location], context: createContext( - action.payload.chapter, - action.payload.externals, + action.payload.library.chapter, + action.payload.library.external.symbols, location ), - externals: action.payload.externals + externalSymbols: action.payload.library.external.symbols, + globals: action.payload.library.globals } } case SEND_REPL_INPUT_TO_OUTPUT: diff --git a/src/sagas/backend.ts b/src/sagas/backend.ts index 1882fd0987..8abc49bbdc 100644 --- a/src/sagas/backend.ts +++ b/src/sagas/backend.ts @@ -1,9 +1,13 @@ +/*eslint no-eval: "error"*/ +/*eslint-env browser*/ import { delay, SagaIterator } from 'redux-saga' import { call, put, select, takeEvery } from 'redux-saga/effects' import * as actions from '../actions' import * as actionTypes from '../actions/actionTypes' import { + AssessmentCategory, + ExternalLibraryName, IAssessment, IAssessmentOverview, IQuestion @@ -122,9 +126,23 @@ const getAssessmentOverviews = async (accessToken: string) => { * @returns {IAssessment} */ const getAssessment = async (id: number, accessToken: string) => { - const assessment: any = await authorizedGet(`assessments/${id}`, accessToken) - assessment.category = capitalise(assessment.type) - delete assessment.type + const assessmentResult: any = await authorizedGet(`assessments/${id}`, accessToken) + const assessment = assessmentResult as IAssessment + /** Fix type -> category */ + assessment.category = capitalise(assessmentResult.type) as AssessmentCategory + delete assessmentResult.type + assessment.questions = assessment.questions.map(q => { + /** Make library.external.name uppercase */ + q.library.external.name = q.library.external.name.toUpperCase() as ExternalLibraryName + /** Make globals into an Array of (string, value) */ + q.library.globals = Object.entries(q.library.globals as object).map(entry => { + try { + entry[1] = (window as any).eval(entry[1]) + } catch (e) {} + return entry + }) + return q + }) return assessment } diff --git a/src/sagas/index.ts b/src/sagas/index.ts index 3ef648d589..dbc099dd15 100644 --- a/src/sagas/index.ts +++ b/src/sagas/index.ts @@ -11,7 +11,7 @@ import { WorkspaceLocation } from '../actions/workspaces' import { ExternalLibraryNames } from '../components/assessment/assessmentShape' import { mockBackendSaga } from '../mocks/backend' import { externalLibraries } from '../reducers/externalLibraries' -import { defaultEditorValue, IState } from '../reducers/states' +import { defaultEditorValue, IState, IWorkspaceState } from '../reducers/states' import { IVLE_KEY, USE_BACKEND } from '../utils/constants' import { showSuccessMessage, showWarningMessage } from '../utils/notification' import backendSaga from './backend' @@ -28,41 +28,73 @@ function* workspaceSaga(): SagaIterator { yield takeEvery(actionTypes.EVAL_EDITOR, function*(action) { const location = (action as actionTypes.IAction).payload.workspaceLocation - const code: string = yield select((state: IState) => state.workspaces[location].editorValue) + const code: string = yield select( + (state: IState) => (state.workspaces[location] as IWorkspaceState).editorValue + ) const chapter: number = yield select( - (state: IState) => state.workspaces[location].context.chapter + (state: IState) => (state.workspaces[location] as IWorkspaceState).context.chapter + ) + const symbols: string[] = yield select( + (state: IState) => (state.workspaces[location] as IWorkspaceState).externalSymbols ) - const externals: string[] = yield select( - (state: IState) => state.workspaces[location].externals + const globals: Array<[string, any]> = yield select( + (state: IState) => (state.workspaces[location] as IWorkspaceState).globals ) + const library = { + chapter, + external: { + name: ExternalLibraryNames.NONE, + symbols + }, + globals + } /** End any code that is running right now. */ yield put(actions.beginInterruptExecution(location)) - /** Clear the context, with the same chapter and externals as before. */ - yield put(actions.clearContext(chapter, externals, ExternalLibraryNames.NONE, location)) + /** Clear the context, with the same chapter and externalSymbols as before. */ + yield put(actions.clearContext(library, location)) yield put(actions.clearReplOutput(location)) - context = yield select((state: IState) => state.workspaces[location].context) + context = yield select( + (state: IState) => (state.workspaces[location] as IWorkspaceState).context + ) yield* evalCode(code, context, location) }) yield takeEvery(actionTypes.EVAL_REPL, function*(action) { const location = (action as actionTypes.IAction).payload.workspaceLocation - const code: string = yield select((state: IState) => state.workspaces[location].replValue) + const code: string = yield select( + (state: IState) => (state.workspaces[location] as IWorkspaceState).replValue + ) yield put(actions.beginInterruptExecution(location)) yield put(actions.clearReplInput(location)) yield put(actions.sendReplInputToOutput(code, location)) - context = yield select((state: IState) => state.workspaces[location].context) + context = yield select( + (state: IState) => (state.workspaces[location] as IWorkspaceState).context + ) yield* evalCode(code, context, location) }) yield takeEvery(actionTypes.CHAPTER_SELECT, function*(action) { const location = (action as actionTypes.IAction).payload.workspaceLocation const newChapter = (action as actionTypes.IAction).payload.chapter - const oldChapter = yield select((state: IState) => state.workspaces[location].context.chapter) - const externals: string[] = yield select( - (state: IState) => state.workspaces[location].externals + const oldChapter = yield select( + (state: IState) => (state.workspaces[location] as IWorkspaceState).context.chapter + ) + const symbols: string[] = yield select( + (state: IState) => (state.workspaces[location] as IWorkspaceState).externalSymbols + ) + const globals: Array<[string, any]> = yield select( + (state: IState) => (state.workspaces[location] as IWorkspaceState).globals ) if (newChapter !== oldChapter) { - yield put(actions.clearContext(newChapter, externals, ExternalLibraryNames.NONE, location)) + const library = { + chapter: newChapter, + external: { + name: ExternalLibraryNames.NONE, + symbols + }, + globals + } + yield put(actions.clearContext(library, location)) yield put(actions.clearReplOutput(location)) yield call(showSuccessMessage, `Switched to Source \xa7${newChapter}`, 1000) } @@ -81,17 +113,30 @@ function* workspaceSaga(): SagaIterator { */ yield takeEvery(actionTypes.PLAYGROUND_EXTERNAL_SELECT, function*(action) { const location = (action as actionTypes.IAction).payload.workspaceLocation - const chapter = yield select((state: IState) => state.workspaces[location].context.chapter) - const newExternal = (action as actionTypes.IAction).payload.external - const oldExternal = yield select( + const chapter = yield select( + (state: IState) => (state.workspaces[location] as IWorkspaceState).context.chapter + ) + const globals: Array<[string, any]> = yield select( + (state: IState) => (state.workspaces[location] as IWorkspaceState).globals + ) + const newExternalLibraryName = (action as actionTypes.IAction).payload.externalLibraryName + const oldExternalLibraryName = yield select( (state: IState) => state.workspaces.playground.playgroundExternal ) - if (newExternal !== oldExternal) { - const externals = externalLibraries.get(newExternal)! - yield put(actions.changePlaygroundExternal(newExternal)) - yield put(actions.clearContext(chapter, externals, newExternal, location)) + const symbols = externalLibraries.get(newExternalLibraryName)! + const library = { + chapter, + external: { + name: newExternalLibraryName, + symbols + }, + globals + } + if (newExternalLibraryName !== oldExternalLibraryName) { + yield put(actions.changePlaygroundExternal(newExternalLibraryName)) + yield put(actions.clearContext(library, location)) yield put(actions.clearReplOutput(location)) - yield call(showSuccessMessage, `Switched to ${newExternal} library`, 1000) + yield call(showSuccessMessage, `Switched to ${newExternalLibraryName} library`, 1000) } }) @@ -101,7 +146,7 @@ function* workspaceSaga(): SagaIterator { * @see clearContext and files under 'public/externalLibs/graphics' */ yield takeEvery(actionTypes.CLEAR_CONTEXT, function*(action) { - const externalLibraryName = (action as actionTypes.IAction).payload.externalLibraryName + const externalLibraryName = (action as actionTypes.IAction).payload.library.external.name const resetWebGl = (window as any).getReadyWebGLForCanvas switch (externalLibraryName) { case ExternalLibraryNames.TWO_DIM_RUNES: @@ -114,6 +159,10 @@ function* workspaceSaga(): SagaIterator { resetWebGl('curve') break } + const globals: Array<[string, any]> = (action as actionTypes.IAction).payload.library.globals + for (const [key, value] of globals) { + window[key] = value + } yield undefined })