diff --git a/public/externalLibs/env_visualizer/visualizer.js b/public/externalLibs/env_visualizer/visualizer.js index 221977fe06..ac935be0e9 100644 --- a/public/externalLibs/env_visualizer/visualizer.js +++ b/public/externalLibs/env_visualizer/visualizer.js @@ -947,8 +947,8 @@ function draw_env(context) { // blink icon - const icon = document.getElementById("Env Visualizer-icon") - icon.classList.add("side-content-header-button-alert") + const icon = document.getElementById("env-icon") + icon.classList.add("side-content-tab-alert") // reset current drawing fnObjectLayer.scene.clear() diff --git a/public/externalLibs/inspector/inspector.js b/public/externalLibs/inspector/inspector.js index 15a441ff5c..3940f91a4f 100644 --- a/public/externalLibs/inspector/inspector.js +++ b/public/externalLibs/inspector/inspector.js @@ -107,12 +107,6 @@ "Symbol(Used to implement hoisting)": " " } - setInterval(()=>{ - if(document.getElementById("inspector-container") != null){ - document.getElementById("Inspector-icon").classList.remove("side-content-header-button-alert"); - } - },1000) - function updateContext(context, stringify) { function dumpTable(env) { var res = ''; @@ -127,10 +121,10 @@ } // icon to blink - const icon = document.getElementById("Inspector-icon"); + const icon = document.getElementById("inspector-icon"); if (!context && icon) { - icon.classList.remove("side-content-header-button-alert"); + icon.classList.remove("side-content-tab-alert"); container.innerHTML = ""; return } @@ -151,7 +145,7 @@ newtable.appendChild(tbody); container.appendChild(newtable); if (icon) { - icon.classList.add("side-content-header-button-alert"); + icon.classList.add("side-content-tab-alert"); } } } catch (e) { diff --git a/src/actions/__tests__/workspaces.ts b/src/actions/__tests__/workspaces.ts index 2e101045fd..6635b4da05 100755 --- a/src/actions/__tests__/workspaces.ts +++ b/src/actions/__tests__/workspaces.ts @@ -6,7 +6,6 @@ import { beginClearContext, browseReplHistoryDown, browseReplHistoryUp, - changeActiveTab, changeEditorHeight, changeEditorWidth, changePlaygroundExternal, @@ -52,18 +51,6 @@ test('browseReplHistoryUp generates correct action object', () => { }); }); -test('changeActiveTab generates correct action object', () => { - const activeTab = 3; - const action = changeActiveTab(activeTab, playgroundWorkspace); - expect(action).toEqual({ - type: actionTypes.CHANGE_ACTIVE_TAB, - payload: { - activeTab, - workspaceLocation: playgroundWorkspace - } - }); -}); - test('changePlaygroundExternal generates correct action object', () => { const newExternal = 'new-external-test'; const action = changePlaygroundExternal(newExternal); diff --git a/src/actions/actionTypes.ts b/src/actions/actionTypes.ts index 546f7f471b..0fec33aed0 100755 --- a/src/actions/actionTypes.ts +++ b/src/actions/actionTypes.ts @@ -32,7 +32,6 @@ export const HIGHLIGHT_LINE = 'HIGHLIGHT_LINE'; export const BEGIN_CLEAR_CONTEXT = 'BEGIN_CLEAR_CONTEXT'; export const BROWSE_REPL_HISTORY_DOWN = 'BROWSE_REPL_HISTORY_DOWN'; export const BROWSE_REPL_HISTORY_UP = 'BROWSE_REPL_HISTORY_UP'; -export const CHANGE_ACTIVE_TAB = 'CHANGE_ACTIVE_TAB'; export const CHANGE_EDITOR_HEIGHT = 'CHANGE_EDITOR_HEIGHT'; export const CHANGE_EDITOR_WIDTH = 'CHANGE_EDITOR_WIDTH'; export const CHANGE_EXEC_TIME = 'CHANGE_EXEC_TIME'; diff --git a/src/actions/workspaces.ts b/src/actions/workspaces.ts index de8f74f82a..bfa996cfce 100755 --- a/src/actions/workspaces.ts +++ b/src/actions/workspaces.ts @@ -38,14 +38,6 @@ export const browseReplHistoryUp: ActionCreator = ( payload: { workspaceLocation } }); -export const changeActiveTab: ActionCreator = ( - activeTab: number, - workspaceLocation: WorkspaceLocation -) => ({ - type: actionTypes.CHANGE_ACTIVE_TAB, - payload: { activeTab, workspaceLocation } -}); - export const changePlaygroundExternal: ActionCreator = ( newExternal: string ) => ({ diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index 2eea0ec4be..7e0fce9ece 100755 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -37,7 +37,6 @@ the REPL. export interface IPlaygroundProps extends IDispatchProps, IStateProps, RouteComponentProps<{}> {} export interface IStateProps { - activeTab: number; editorSessionId: string; editorValue: string; editorHeight?: number; @@ -63,7 +62,6 @@ export interface IStateProps { export interface IDispatchProps { handleBrowseHistoryDown: () => void; handleBrowseHistoryUp: () => void; - handleChangeActiveTab: (activeTab: number) => void; handleChangeExecTime: (execTime: number) => void; handleChapterSelect: (chapter: number) => void; handleEditorEval: () => void; @@ -173,8 +171,7 @@ class Playground extends React.Component { }, sideContentHeight: this.props.sideContentHeight, sideContentProps: { - activeTab: this.props.activeTab, - handleChangeActiveTab: this.props.handleChangeActiveTab, + defaultSelectedTabId: 'introduction', tabs: [playgroundIntroductionTab, listVisualizerTab, inspectorTab, envVisualizerTab] } }; @@ -200,26 +197,30 @@ class Playground extends React.Component { const playgroundIntroductionTab: SideContentTab = { label: 'Introduction', - icon: IconNames.COMPASS, - body: + iconName: IconNames.COMPASS, + body: , + id: 'introduction' }; const listVisualizerTab: SideContentTab = { label: 'Data Visualizer', - icon: IconNames.EYE_OPEN, - body: + iconName: IconNames.EYE_OPEN, + body: , + id: 'data' }; const inspectorTab: SideContentTab = { label: 'Inspector', - icon: IconNames.SEARCH, - body: + iconName: IconNames.SEARCH, + body: , + id: 'inspector' }; const envVisualizerTab: SideContentTab = { label: 'Env Visualizer', - icon: IconNames.GLOBE, - body: + iconName: IconNames.GLOBE, + body: , + id: 'env' }; export default Playground; diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index add5e7d708..bb8a2c9b34 100755 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -13,7 +13,6 @@ const baseProps = { isRunning: false, isDebugging: false, enableDebugging: true, - activeTab: 0, editorSessionId: '', editorWidth: '50%', isEditorAutorun: false, @@ -27,7 +26,6 @@ const baseProps = { websocketStatus: 0, handleBrowseHistoryDown: () => {}, handleBrowseHistoryUp: () => {}, - handleChangeActiveTab: (n: number) => {}, handleChangeExecTime: (execTime: number) => {}, handleChapterSelect: (chapter: number) => {}, handleEditorEval: () => {}, diff --git a/src/components/academy/grading/GradingWorkspace.tsx b/src/components/academy/grading/GradingWorkspace.tsx index d2e48e5fc0..7fa0a0b8a3 100755 --- a/src/components/academy/grading/GradingWorkspace.tsx +++ b/src/components/academy/grading/GradingWorkspace.tsx @@ -26,7 +26,6 @@ import { Grading, IAnsweredQuestion } from './gradingShape'; export type GradingWorkspaceProps = DispatchProps & OwnProps & StateProps; export type StateProps = { - activeTab: number; autogradingResults: AutogradingResult[]; grading?: Grading; editorPrepend: string; @@ -56,7 +55,6 @@ export type OwnProps = { export type DispatchProps = { handleBrowseHistoryDown: () => void; handleBrowseHistoryUp: () => void; - handleChangeActiveTab: (activeTab: number) => void; handleChapterSelect: (chapter: any, changeEvent: any) => void; handleClearContext: (library: Library) => void; handleEditorEval: () => void; @@ -240,12 +238,10 @@ class GradingWorkspace extends React.Component { props: GradingWorkspaceProps, questionId: number ) => ({ - activeTab: props.activeTab, - handleChangeActiveTab: props.handleChangeActiveTab, tabs: [ { label: `Grading: Question ${questionId}`, - icon: IconNames.TICK, + iconName: IconNames.TICK, /* Render an editor with the xp given to the current question. */ body: ( { maxXp={props.grading![questionId].question.maxXp} studentName={props.grading![questionId].student.name} /> - ) + ), + id: 'grading' }, { label: `Task ${questionId + 1}`, - icon: IconNames.NINJA, - body: + iconName: IconNames.NINJA, + body: , + id: 'question_overview' }, { label: `Chat`, - icon: IconNames.CHAT, + iconName: IconNames.CHAT, body: USE_CHATKIT ? ( ) : ( - ChatKit disabled. - ) + Chatkit disabled. + ), + id: 'chat', + disabled: !USE_CHATKIT }, { label: `Autograder`, - icon: IconNames.AIRPLANE, + iconName: IconNames.AIRPLANE, body: ( - ) + ), + id: 'autograder' } ] }); diff --git a/src/components/assessment/AssessmentWorkspace.tsx b/src/components/assessment/AssessmentWorkspace.tsx index df64d508c7..44e21998fe 100755 --- a/src/components/assessment/AssessmentWorkspace.tsx +++ b/src/components/assessment/AssessmentWorkspace.tsx @@ -21,7 +21,7 @@ import { controlButton } from '../commons'; import Markdown from '../commons/Markdown'; import Workspace, { WorkspaceProps } from '../workspace'; import { ControlBarProps } from '../workspace/ControlBar'; -import { SideContentProps } from '../workspace/side-content'; +import { SideContentProps, SideContentTab } from '../workspace/side-content'; import Autograder from '../workspace/side-content/Autograder'; import ToneMatrix from '../workspace/side-content/ToneMatrix'; import { @@ -39,7 +39,6 @@ import GradingResult from './GradingResult'; export type AssessmentWorkspaceProps = DispatchProps & OwnProps & StateProps; export type StateProps = { - activeTab: number; assessment?: IAssessment; autogradingResults: AutogradingResult[]; editorPrepend: string; @@ -72,7 +71,6 @@ export type DispatchProps = { handleAssessmentFetch: (assessmentId: number) => void; handleBrowseHistoryDown: () => void; handleBrowseHistoryUp: () => void; - handleChangeActiveTab: (activeTab: number) => void; handleChapterSelect: (chapter: any, changeEvent: any) => void; handleClearContext: (library: Library) => void; handleEditorEval: () => void; @@ -328,27 +326,30 @@ class AssessmentWorkspace extends React.Component< props: AssessmentWorkspaceProps, questionId: number ) => { - const tabs = [ + const tabs: SideContentTab[] = [ { label: `Task ${questionId + 1}`, - icon: IconNames.NINJA, - body: + iconName: IconNames.NINJA, + body: , + id: 'question_overview' }, { label: `${props.assessment!.category} Briefing`, - icon: IconNames.BRIEFCASE, - body: + iconName: IconNames.BRIEFCASE, + body: , + id: 'briefing' }, { label: `${props.assessment!.category} Autograder`, - icon: IconNames.AIRPLANE, + iconName: IconNames.AIRPLANE, body: ( - ) + ), + id: 'autograder' } ]; const isGraded = props.assessment!.questions[questionId].grader !== null; @@ -356,7 +357,7 @@ class AssessmentWorkspace extends React.Component< tabs.push( { label: `Grading`, - icon: IconNames.TICK, + iconName: IconNames.TICK, body: ( - ) + ), + id: 'grading' }, { label: `Chat`, - icon: IconNames.CHAT, + iconName: IconNames.CHAT, body: USE_CHATKIT ? ( ) : ( Chatkit disabled. - ) + ), + id: 'chat', + disabled: !USE_CHATKIT } ); } @@ -387,15 +391,12 @@ class AssessmentWorkspace extends React.Component< if (functionsAttached.includes('get_matrix')) { tabs.push({ label: `Tone Matrix`, - icon: IconNames.GRID_VIEW, - body: + iconName: IconNames.GRID_VIEW, + body: , + id: 'tone_matrix' }); } - return { - activeTab: props.activeTab, - handleChangeActiveTab: props.handleChangeActiveTab, - tabs - }; + return { tabs }; }; /** Pre-condition: IAssessment has been loaded */ diff --git a/src/components/assessment/__tests__/AssessmentWorkspace.tsx b/src/components/assessment/__tests__/AssessmentWorkspace.tsx index 56b04e9fd4..85c7bc66d5 100644 --- a/src/components/assessment/__tests__/AssessmentWorkspace.tsx +++ b/src/components/assessment/__tests__/AssessmentWorkspace.tsx @@ -6,7 +6,6 @@ import { Library } from '../assessmentShape'; import AssessmentWorkspace, { AssessmentWorkspaceProps } from '../AssessmentWorkspace'; const defaultProps: AssessmentWorkspaceProps = { - activeTab: 0, assessmentId: 0, autogradingResults: [], notAttempted: true, @@ -22,7 +21,6 @@ const defaultProps: AssessmentWorkspaceProps = { handleAssessmentFetch: (assessmentId: number) => {}, handleBrowseHistoryDown: () => {}, handleBrowseHistoryUp: () => {}, - handleChangeActiveTab: (activeTab: number) => {}, handleChapterSelect: (chapter: any, changeEvent: any) => {}, handleClearContext: (library: Library) => {}, handleEditorEval: () => {}, diff --git a/src/components/missionControl/EditingWorkspace.tsx b/src/components/missionControl/EditingWorkspace.tsx index 4699540e16..8b23d632ac 100644 --- a/src/components/missionControl/EditingWorkspace.tsx +++ b/src/components/missionControl/EditingWorkspace.tsx @@ -19,7 +19,7 @@ import { controlButton } from '../commons'; import Markdown from '../commons/Markdown'; import Workspace, { WorkspaceProps } from '../workspace'; import { ControlBarProps } from '../workspace/ControlBar'; -import { SideContentProps } from '../workspace/side-content'; +import { SideContentProps, SideContentTab } from '../workspace/side-content'; import ToneMatrix from '../workspace/side-content/ToneMatrix'; import { AutograderTab, @@ -39,7 +39,6 @@ import { export type AssessmentWorkspaceProps = DispatchProps & OwnProps & StateProps; export type StateProps = { - activeTab: number; editorHeight?: number; editorValue: string | null; editorWidth: string; @@ -93,7 +92,6 @@ export type DispatchProps = { interface IState { assessment: IAssessment | null; - activeTab: number; editingMode: string; hasUnsavedChanges: boolean; showResetTemplateOverlay: boolean; @@ -106,7 +104,6 @@ class AssessmentWorkspace extends React.Component { - this.setState({ - activeTab: tab - }); - }; - private toggleEditingMode = () => { const toggle = this.state.editingMode === 'question' ? 'global' : 'question'; this.setState({ - activeTab: 0, editingMode: toggle }); }; @@ -414,7 +404,7 @@ class AssessmentWorkspace extends React.Component { const assessment = this.state.assessment!; - let tabs; + let tabs: SideContentTab[]; if (this.state.editingMode === 'question') { const qnType = this.state.assessment!.questions[this.props.questionId].type; const questionTemplateTab = @@ -438,23 +428,25 @@ class AssessmentWorkspace extends React.Component - ) + ), + id: 'question_overview' }, { label: `Question Template`, - icon: IconNames.DOCUMENT, - body: questionTemplateTab + iconName: IconNames.DOCUMENT, + body: questionTemplateTab, + id: 'question_template' }, { label: `Manage Local Deployment`, - icon: IconNames.HOME, + iconName: IconNames.HOME, body: ( - ) + ), + id: 'local_deployment' }, { label: `Manage Local Grader Deployment`, - icon: IconNames.CONFIRM, + iconName: IconNames.CONFIRM, body: ( - ) + ), + id: 'local_grader_deployment' }, { label: `Grading`, - icon: IconNames.TICK, + iconName: IconNames.TICK, body: ( - ) + ), + id: 'grading' } ]; if (qnType === 'programming') { tabs.push({ label: `Autograder`, - icon: IconNames.AIRPLANE, + iconName: IconNames.AIRPLANE, body: ( - ) + ), + id: 'autograder' }); } const functionsAttached = assessment!.questions[questionId].library.external.symbols; if (functionsAttached.includes('get_matrix')) { tabs.push({ label: `Tone Matrix`, - icon: IconNames.GRID_VIEW, - body: + iconName: IconNames.GRID_VIEW, + body: , + id: 'tone_matrix' }); } } else { tabs = [ { label: `${assessment!.category} Briefing`, - icon: IconNames.BRIEFCASE, + iconName: IconNames.BRIEFCASE, body: ( - ) + ), + id: 'briefing' }, { label: `Manage Question`, - icon: IconNames.WRENCH, + iconName: IconNames.WRENCH, body: ( - ) + ), + id: 'manage_question' }, { label: `Manage Global Deployment`, - icon: IconNames.GLOBE, + iconName: IconNames.GLOBE, body: ( - ) + ), + id: 'global_deployment' }, { label: `Manage Global Grader Deployment`, - icon: IconNames.CONFIRM, + iconName: IconNames.CONFIRM, body: ( - ) + ), + id: 'global_grader_deployment' } ]; } - return { - activeTab: this.state.activeTab, - handleChangeActiveTab: this.handleChangeActiveTab, - tabs - }; + return { tabs }; }; /** Pre-condition: IAssessment has been loaded */ diff --git a/src/components/missionControl/editingWorkspaceSideContent/DeploymentTab.tsx b/src/components/missionControl/editingWorkspaceSideContent/DeploymentTab.tsx index 2225a409f3..dd2a327701 100644 --- a/src/components/missionControl/editingWorkspaceSideContent/DeploymentTab.tsx +++ b/src/components/missionControl/editingWorkspaceSideContent/DeploymentTab.tsx @@ -1,4 +1,4 @@ -import { Button, Classes, MenuItem, Switch } from '@blueprintjs/core'; +import { Button, Classes, Divider, MenuItem, Switch } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { ItemRenderer, Select } from '@blueprintjs/select'; import * as React from 'react'; @@ -7,7 +7,6 @@ import { sourceChapters } from '../../../reducers/states'; import { ExternalLibraryName, IAssessment, Library } from '../../assessment/assessmentShape'; import { controlButton } from '../../commons'; -import SideContent from '../../workspace/side-content'; import { emptyLibrary } from '../assessmentTemplates'; import { assignToPath, getValueFromPath } from './'; import TextareaContent from './TextareaContent'; @@ -33,14 +32,7 @@ interface IExternal { symbols: string[]; } -export class DeploymentTab extends React.Component { - public constructor(props: IProps) { - super(props); - this.state = { - activeTab: 0 - }; - } - +export class DeploymentTab extends React.Component { public render() { if (!this.props.isOptionalDeployment) { return ( @@ -98,8 +90,7 @@ export class DeploymentTab extends React.Component {externalSelect(deployment.external.name, this.handleExternalSelect!)} -
-
+
Symbols:

@@ -120,43 +111,24 @@ export class DeploymentTab extends React.Component ); - const tabs = [ - { - label: `Library`, - icon: IconNames.BOOK, - body: symbolsFragment - }, - { - label: `Globals`, - icon: IconNames.GLOBE, - body: globalsFragment - } - ]; - return (
{/* {deploymentDisp}
*/} + {resetLibrary} -
+ Interpreter:
{chapterSelect(deployment.chapter, this.handleChapterSelect)} - + + {symbolsFragment} + + {globalsFragment}
); }; - private handleChangeActiveTab = (tab: number) => { - this.setState({ - activeTab: tab - }); - }; - private textareaContent = (path: Array) => { return ( ) => void; } -interface IState { - activeTab: number; +interface IQuestionEditorState { + activeEditor: QuestionEditor; templateValue: string; templateFocused: boolean; } -const tabPaths = ['prepend', 'postpend', 'solutionTemplate', 'answer']; - -export class ProgrammingQuestionTemplateTab extends React.Component { - public constructor(props: IProps) { +const questionEditorPaths = ['prepend', 'postpend', 'solutionTemplate', 'answer'] as const; + +export type QuestionEditorId = typeof questionEditorPaths[number]; + +const QuestionEditorSelect = Select.ofType(); + +export type QuestionEditor = { + label: string; + icon: IconName; + id: QuestionEditorId; +}; + +const questionEditors: QuestionEditor[] = [ + { + label: 'Prepend', + icon: IconNames.CHEVRON_UP, + id: 'prepend' + }, + { + label: 'Postpend', + icon: IconNames.CHEVRON_DOWN, + id: 'postpend' + }, + { + label: 'Solution Template', + icon: IconNames.MANUAL, + id: 'solutionTemplate' + }, + { + label: 'Suggested Answer', + icon: IconNames.TICK, + id: 'answer' + } +]; + +/* + * activeEditor is the default editor to show initially + */ +export class ProgrammingQuestionTemplateTab extends React.Component< + IQuestionEditorProps, + IQuestionEditorState +> { + public constructor(props: IQuestionEditorProps) { super(props); this.state = { - activeTab: 0, + activeEditor: questionEditors[0], templateValue: '', templateFocused: false }; @@ -42,7 +81,7 @@ export class ProgrammingQuestionTemplateTab extends React.Component { const qnPath = ['questions', this.props.questionId]; - const path = qnPath.concat([tabPaths[this.state.activeTab]]); + const path = qnPath.concat(this.state.activeEditor.id); const copyFromEditorButton = controlButton( 'Copy from Editor', @@ -56,45 +95,53 @@ export class ProgrammingQuestionTemplateTab extends React.Component {copyFromEditorButton} {copyToEditorButton} -
-
+ {this.editor(path)} ); - const tabs = [ - { - label: `Prepend`, - icon: IconNames.CHEVRON_UP, - body: tabComponent - }, - { - label: `Postpend`, - icon: IconNames.CHEVRON_DOWN, - body: tabComponent - }, - { - label: `Solution Template`, - icon: IconNames.MANUAL, - body: tabComponent - }, - { - label: `Suggested Answer`, - icon: IconNames.TICK, - body: tabComponent - } - ]; + const menuRenderer: ItemRenderer = (editor, { handleClick }) => ( + + ); + + const editorSelect = ( + currentEditor: QuestionEditor, + handleSelect: (i: QuestionEditor) => void + ) => ( + +