diff --git a/src/commons/application/ApplicationTypes.ts b/src/commons/application/ApplicationTypes.ts index 2abb76b065..e6c4978ba3 100644 --- a/src/commons/application/ApplicationTypes.ts +++ b/src/commons/application/ApplicationTypes.ts @@ -88,7 +88,22 @@ export type ErrorOutput = { consoleLogs: string[]; }; -export type InterpreterOutput = RunningOutput | CodeOutput | ResultOutput | ErrorOutput; +/** + * An output which represents a message being displayed to the user. Not a true + * result from the program, but rather a customised notification meant to highlight + * events that occur outside execution of the program. + */ +export type NotificationOutput = { + type: 'notification'; + consoleLog: string; +}; + +export type InterpreterOutput = + | RunningOutput + | CodeOutput + | ResultOutput + | ErrorOutput + | NotificationOutput; export enum Role { Student = 'student', @@ -360,6 +375,9 @@ export const createDefaultWorkspace = (workspaceLocation: WorkspaceLocation): Wo originalValue: '' }, replValue: '', + hasTokenCounter: false, + tokenCount: 0, + customNotification: '', sharedbConnected: false, stepLimit: 1000, globals: [], diff --git a/src/commons/application/actions/__tests__/SessionActions.ts b/src/commons/application/actions/__tests__/SessionActions.ts index 9e3773434a..ff15090d99 100644 --- a/src/commons/application/actions/__tests__/SessionActions.ts +++ b/src/commons/application/actions/__tests__/SessionActions.ts @@ -274,6 +274,7 @@ test('setAssessmentConfigurations generates correct action object', () => { type: 'Mission1', isManuallyGraded: true, displayInDashboard: true, + hasTokenCounter: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, @@ -282,6 +283,7 @@ test('setAssessmentConfigurations generates correct action object', () => { type: 'Mission2', isManuallyGraded: true, displayInDashboard: true, + hasTokenCounter: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, @@ -290,6 +292,7 @@ test('setAssessmentConfigurations generates correct action object', () => { type: 'Mission3', isManuallyGraded: true, displayInDashboard: true, + hasTokenCounter: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 } @@ -640,6 +643,7 @@ test('updateAssessmentTypes generates correct action object', () => { type: 'Missions', isManuallyGraded: true, displayInDashboard: true, + hasTokenCounter: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, @@ -648,6 +652,7 @@ test('updateAssessmentTypes generates correct action object', () => { type: 'Quests', isManuallyGraded: true, displayInDashboard: true, + hasTokenCounter: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, @@ -656,6 +661,7 @@ test('updateAssessmentTypes generates correct action object', () => { type: 'Paths', isManuallyGraded: true, displayInDashboard: true, + hasTokenCounter: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, @@ -664,6 +670,7 @@ test('updateAssessmentTypes generates correct action object', () => { type: 'Contests', isManuallyGraded: true, displayInDashboard: true, + hasTokenCounter: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, @@ -672,6 +679,7 @@ test('updateAssessmentTypes generates correct action object', () => { type: 'Others', isManuallyGraded: true, displayInDashboard: true, + hasTokenCounter: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 } @@ -689,6 +697,7 @@ test('deleteAssessmentConfig generates correct action object', () => { type: 'Mission1', isManuallyGraded: true, displayInDashboard: true, + hasTokenCounter: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }; diff --git a/src/commons/assessment/AssessmentTypes.ts b/src/commons/assessment/AssessmentTypes.ts index 6b1144e931..a4cdb9f43b 100644 --- a/src/commons/assessment/AssessmentTypes.ts +++ b/src/commons/assessment/AssessmentTypes.ts @@ -81,6 +81,7 @@ export type Assessment = { type: AssessmentType; globalDeployment?: Library; // For mission control graderDeployment?: Library; // For mission control + hasTokenCounter?: boolean; id: number; longSummary: string; missionPDF: string; @@ -95,6 +96,7 @@ export type AssessmentConfiguration = { displayInDashboard: boolean; hoursBeforeEarlyXpDecay: number; earlySubmissionXp: number; + hasTokenCounter: boolean; }; export interface IProgrammingQuestion extends BaseQuestion { diff --git a/src/commons/assessment/__tests__/Assessment.tsx b/src/commons/assessment/__tests__/Assessment.tsx index 18eb8dcb85..bb7aee31c9 100644 --- a/src/commons/assessment/__tests__/Assessment.tsx +++ b/src/commons/assessment/__tests__/Assessment.tsx @@ -17,6 +17,7 @@ const mockAssessmentProps = assertType()({ type: 'Missions', isManuallyGraded: true, displayInDashboard: true, + hasTokenCounter: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 } diff --git a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx index 7aec87268c..63bbecb71b 100644 --- a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx +++ b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx @@ -76,6 +76,8 @@ import { changeExecTime, changeSideContentHeight, clearReplOutput, + disableTokenCounter, + enableTokenCounter, evalEditor, evalRepl, evalTestcase, @@ -111,6 +113,7 @@ const AssessmentWorkspace: React.FC = props => { const { isMobileBreakpoint } = useResponsive(); const assessment = useTypedSelector(state => state.session.assessments.get(props.assessmentId)); + const [selectedTab, setSelectedTab] = useState( assessment?.questions[props.questionId].grader !== undefined ? SideContentType.grading @@ -149,7 +152,9 @@ const AssessmentWorkspace: React.FC = props => { handleEditorUpdateBreakpoints, handleReplEval, handleSave, - handleUpdateHasUnsavedChanges + handleUpdateHasUnsavedChanges, + handleEnableTokenCounter, + handleDisableTokenCounter } = useMemo(() => { return { handleTestcaseEval: (id: number) => dispatch(evalTestcase(workspaceLocation, id)), @@ -173,7 +178,9 @@ const AssessmentWorkspace: React.FC = props => { handleSave: (id: number, answer: number | string | ContestEntry[]) => dispatch(submitAnswer(id, answer)), handleUpdateHasUnsavedChanges: (hasUnsavedChanges: boolean) => - dispatch(updateHasUnsavedChanges(workspaceLocation, hasUnsavedChanges)) + dispatch(updateHasUnsavedChanges(workspaceLocation, hasUnsavedChanges)), + handleEnableTokenCounter: () => dispatch(enableTokenCounter(workspaceLocation)), + handleDisableTokenCounter: () => dispatch(disableTokenCounter(workspaceLocation)) }; }, [dispatch]); @@ -238,6 +245,21 @@ const AssessmentWorkspace: React.FC = props => { checkWorkspaceReset(); }); + /** + * Handles toggling enabling and disabling token counter depending on assessment properties + */ + useEffect(() => { + if (props.assessmentConfiguration.hasTokenCounter) { + handleEnableTokenCounter(); + } else { + handleDisableTokenCounter(); + } + }, [ + props.assessmentConfiguration.hasTokenCounter, + handleEnableTokenCounter, + handleDisableTokenCounter + ]); + /** * Handles toggling of relevant SideContentTabs when mobile breakpoint it hit */ @@ -839,7 +861,6 @@ const AssessmentWorkspace: React.FC = props => { sideBarProps: sideBarProps, mobileSideContentProps: mobileSideContentProps(questionId) }; - return (
{overlay} diff --git a/src/commons/assessmentWorkspace/__tests__/AssessmentWorkspace.tsx b/src/commons/assessmentWorkspace/__tests__/AssessmentWorkspace.tsx index 35d4fe14ac..6ffcf33954 100644 --- a/src/commons/assessmentWorkspace/__tests__/AssessmentWorkspace.tsx +++ b/src/commons/assessmentWorkspace/__tests__/AssessmentWorkspace.tsx @@ -26,6 +26,7 @@ const defaultProps = assertType()({ type: 'Missions', isManuallyGraded: true, displayInDashboard: true, + hasTokenCounter: false, hoursBeforeEarlyXpDecay: 48, earlySubmissionXp: 200 }, diff --git a/src/commons/mocks/AssessmentMocks.ts b/src/commons/mocks/AssessmentMocks.ts index 5f7deaca51..a16d45484a 100644 --- a/src/commons/mocks/AssessmentMocks.ts +++ b/src/commons/mocks/AssessmentMocks.ts @@ -22,6 +22,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: false, earlySubmissionXp: 200 }, { @@ -30,6 +31,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: false, earlySubmissionXp: 200 }, { @@ -38,6 +40,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: false, earlySubmissionXp: 200 }, { @@ -46,6 +49,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: true, earlySubmissionXp: 200 }, { @@ -54,6 +58,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: false, earlySubmissionXp: 200 } ], @@ -64,6 +69,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: false, earlySubmissionXp: 200 }, { @@ -72,6 +78,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: false, earlySubmissionXp: 200 }, { @@ -80,6 +87,7 @@ export const mockAssessmentConfigurations: AssessmentConfiguration[][] = [ isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: false, earlySubmissionXp: 200 } ] diff --git a/src/commons/profile/__tests__/Profile.tsx b/src/commons/profile/__tests__/Profile.tsx index 7a75913f24..55583cb291 100644 --- a/src/commons/profile/__tests__/Profile.tsx +++ b/src/commons/profile/__tests__/Profile.tsx @@ -25,6 +25,7 @@ const assessmentConfigurations: AssessmentConfiguration[] = [ type: c, isManuallyGraded: false, displayInDashboard: false, + hasTokenCounter: false, hoursBeforeEarlyXpDecay: 0, earlySubmissionXp: 0 })); diff --git a/src/commons/repl/Repl.tsx b/src/commons/repl/Repl.tsx index acf1588770..7f30da1b89 100644 --- a/src/commons/repl/Repl.tsx +++ b/src/commons/repl/Repl.tsx @@ -122,6 +122,12 @@ export const Output: React.FC = (props: OutputProps) => { ); } + case 'notification': + return ( + +
{'💡 ' + props.output.consoleLog}
+
+ ); default: return ''; } diff --git a/src/commons/sagas/BackendSaga.ts b/src/commons/sagas/BackendSaga.ts index 580e155c0e..6fc3e2f98e 100644 --- a/src/commons/sagas/BackendSaga.ts +++ b/src/commons/sagas/BackendSaga.ts @@ -777,7 +777,6 @@ function* BackendSaga(): SagaIterator { getAssessmentConfigs, tokens ); - if (assessmentConfigs) { yield put(actions.setAssessmentConfigurations(assessmentConfigs)); } @@ -982,6 +981,7 @@ function* BackendSaga(): SagaIterator { isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 0, + hasTokenCounter: false, earlySubmissionXp: 0 } ]; diff --git a/src/commons/sagas/WorkspaceSaga.ts b/src/commons/sagas/WorkspaceSaga.ts index 75e0a26199..ad1bb1c3f7 100644 --- a/src/commons/sagas/WorkspaceSaga.ts +++ b/src/commons/sagas/WorkspaceSaga.ts @@ -1,3 +1,4 @@ +import { tokenizer } from 'acorn'; import { FSModule } from 'browserfs/dist/node/core/FS'; import { Context, @@ -9,7 +10,7 @@ import { runFilesInContext, runInContext } from 'js-slang'; -import { TRY_AGAIN } from 'js-slang/dist/constants'; +import { ACORN_PARSE_OPTIONS, TRY_AGAIN } from 'js-slang/dist/constants'; import { defineSymbol } from 'js-slang/dist/createContext'; import { InterruptedError } from 'js-slang/dist/errors/errors'; import { parse } from 'js-slang/dist/parser/parser'; @@ -1256,6 +1257,13 @@ export function* evalCode( yield* dumpDisplayBuffer(workspaceLocation, isStoriesBlock, storyEnv); + // Change token count if its assessment and EVAL_EDITOR + if (actionType === EVAL_EDITOR && workspaceLocation === 'assessment') { + const tokens = [...tokenizer(entrypointCode, ACORN_PARSE_OPTIONS)]; + const tokenCounter = tokens.length; + yield put(actions.setTokenCount(workspaceLocation, tokenCounter)); + } + // Do not write interpreter output to REPL, if executing chunks (e.g. prepend/postpend blocks) if (actionType !== EVAL_SILENT) { if (!isStoriesBlock) { diff --git a/src/commons/sagas/__tests__/BackendSaga.ts b/src/commons/sagas/__tests__/BackendSaga.ts index 5525f1b47c..4bc740624f 100644 --- a/src/commons/sagas/__tests__/BackendSaga.ts +++ b/src/commons/sagas/__tests__/BackendSaga.ts @@ -218,6 +218,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [ isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: false, earlySubmissionXp: 200 }, { @@ -226,6 +227,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [ isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: false, earlySubmissionXp: 200 }, { @@ -234,6 +236,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [ isManuallyGraded: false, displayInDashboard: false, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: false, earlySubmissionXp: 200 }, { @@ -242,6 +245,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [ isManuallyGraded: false, displayInDashboard: false, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: true, earlySubmissionXp: 200 }, { @@ -250,6 +254,7 @@ const mockAssessmentConfigurations: AssessmentConfiguration[] = [ isManuallyGraded: true, displayInDashboard: false, hoursBeforeEarlyXpDecay: 48, + hasTokenCounter: false, earlySubmissionXp: 200 } ]; @@ -1002,6 +1007,7 @@ describe('Test CREATE_COURSE action', () => { isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 0, + hasTokenCounter: false, earlySubmissionXp: 0 } ]; diff --git a/src/commons/workspace/WorkspaceActions.ts b/src/commons/workspace/WorkspaceActions.ts index 14e805a81f..664382ab8a 100644 --- a/src/commons/workspace/WorkspaceActions.ts +++ b/src/commons/workspace/WorkspaceActions.ts @@ -27,7 +27,9 @@ import { CLEAR_REPL_INPUT, CLEAR_REPL_OUTPUT, CLEAR_REPL_OUTPUT_LAST, + DISABLE_TOKEN_COUNTER, EditorTabState, + ENABLE_TOKEN_COUNTER, END_CLEAR_CONTEXT, EVAL_EDITOR, EVAL_EDITOR_AND_TESTCASES, @@ -46,6 +48,7 @@ import { RESET_WORKSPACE, SEND_REPL_INPUT_TO_OUTPUT, SET_FOLDER_MODE, + SET_TOKEN_COUNT, SHIFT_EDITOR_TAB, SubmissionsTableFilters, TOGGLE_EDITOR_AUTORUN, @@ -72,6 +75,9 @@ import { WorkspaceState } from './WorkspaceTypes'; +export const setTokenCount = (workspaceLocation: WorkspaceLocation, tokenCount: number) => + action(SET_TOKEN_COUNT, { workspaceLocation, tokenCount }); + export const browseReplHistoryDown = (workspaceLocation: WorkspaceLocation) => action(BROWSE_REPL_HISTORY_DOWN, { workspaceLocation }); @@ -178,6 +184,12 @@ export const evalTestcase = (workspaceLocation: WorkspaceLocation, testcaseId: n export const runAllTestcases = (workspaceLocation: WorkspaceLocation) => action(EVAL_EDITOR_AND_TESTCASES, { workspaceLocation }); +export const enableTokenCounter = (workspaceLocation: WorkspaceLocation) => + action(ENABLE_TOKEN_COUNTER, { workspaceLocation }); + +export const disableTokenCounter = (workspaceLocation: WorkspaceLocation) => + action(DISABLE_TOKEN_COUNTER, { workspaceLocation }); + export const toggleFolderMode = (workspaceLocation: WorkspaceLocation) => action(TOGGLE_FOLDER_MODE, { workspaceLocation }); diff --git a/src/commons/workspace/WorkspaceReducer.ts b/src/commons/workspace/WorkspaceReducer.ts index eaf1a4bf89..29fdcf3dbd 100644 --- a/src/commons/workspace/WorkspaceReducer.ts +++ b/src/commons/workspace/WorkspaceReducer.ts @@ -10,6 +10,7 @@ import { defaultWorkspaceManager, ErrorOutput, InterpreterOutput, + NotificationOutput, ResultOutput } from '../application/ApplicationTypes'; import { LOG_OUT } from '../application/types/CommonsTypes'; @@ -43,7 +44,9 @@ import { CLEAR_REPL_INPUT, CLEAR_REPL_OUTPUT, CLEAR_REPL_OUTPUT_LAST, + DISABLE_TOKEN_COUNTER, EditorTabState, + ENABLE_TOKEN_COUNTER, END_CLEAR_CONTEXT, EVAL_EDITOR, EVAL_REPL, @@ -57,6 +60,7 @@ import { RESET_WORKSPACE, SEND_REPL_INPUT_TO_OUTPUT, SET_FOLDER_MODE, + SET_TOKEN_COUNT, SHIFT_EDITOR_TAB, TOGGLE_EDITOR_AUTORUN, TOGGLE_UPDATE_ENV, @@ -122,6 +126,15 @@ export const WorkspaceReducer: Reducer = ( } switch (action.type) { + case SET_TOKEN_COUNT: + return { + ...state, + [workspaceLocation]: { + ...state[workspaceLocation], + tokenCount: action.payload.tokenCount + } + }; + case BROWSE_REPL_HISTORY_DOWN: if (state[workspaceLocation].replHistory.browseIndex === null) { // Not yet started browsing history, nothing to do @@ -334,6 +347,22 @@ export const WorkspaceReducer: Reducer = ( ...defaultWorkspaceManager, playground: playgroundWorkspace }; + case ENABLE_TOKEN_COUNTER: + return { + ...state, + [workspaceLocation]: { + ...state[workspaceLocation], + hasTokenCounter: true + } + }; + case DISABLE_TOKEN_COUNTER: + return { + ...state, + [workspaceLocation]: { + ...state[workspaceLocation], + hasTokenCounter: false + } + }; case EVAL_EDITOR: return { ...state, @@ -353,6 +382,7 @@ export const WorkspaceReducer: Reducer = ( }; case EVAL_INTERPRETER_SUCCESS: const execType = state[workspaceLocation].context.executionMethod; + const tokens = state[workspaceLocation].tokenCount; const newOutputEntry: Partial = { type: action.payload.type as 'result' | undefined, value: execType === 'interpreter' ? action.payload.value : stringify(action.payload.value) @@ -360,10 +390,27 @@ export const WorkspaceReducer: Reducer = ( lastOutput = state[workspaceLocation].output.slice(-1)[0]; if (lastOutput !== undefined && lastOutput.type === 'running') { - newOutput = state[workspaceLocation].output.slice(0, -1).concat({ + const newOutputEntryWithLogs = { consoleLogs: lastOutput.consoleLogs, ...newOutputEntry - } as ResultOutput); + } as ResultOutput; + const notificationOutputs: NotificationOutput[] = []; + if (state[workspaceLocation].hasTokenCounter) { + notificationOutputs.push({ + consoleLog: `This program has ${tokens} tokens.`, + type: 'notification' + }); + } + const customNotification = state[workspaceLocation].customNotification; + if (customNotification !== '') { + notificationOutputs.push({ + consoleLog: customNotification, + type: 'notification' + }); + } + newOutput = state[workspaceLocation].output + .slice(0, -1) + .concat([...notificationOutputs, newOutputEntryWithLogs]); } else { newOutput = state[workspaceLocation].output.concat({ consoleLogs: [], diff --git a/src/commons/workspace/WorkspaceTypes.ts b/src/commons/workspace/WorkspaceTypes.ts index 5fb1a0e243..81998af787 100644 --- a/src/commons/workspace/WorkspaceTypes.ts +++ b/src/commons/workspace/WorkspaceTypes.ts @@ -21,6 +21,8 @@ export const CLEAR_REPL_INPUT = 'CLEAR_REPL_INPUT'; export const CLEAR_REPL_OUTPUT = 'CLEAR_REPL_OUTPUT'; export const CLEAR_REPL_OUTPUT_LAST = 'CLEAR_REPL_OUTPUT_LAST'; export const END_CLEAR_CONTEXT = 'END_CLEAR_CONTEXT'; +export const ENABLE_TOKEN_COUNTER = 'ENABLE_TOKEN_COUNTER'; +export const DISABLE_TOKEN_COUNTER = 'DISABLE_TOKEN_COUNTER'; export const EVAL_EDITOR = 'EVAL_EDITOR'; export const EVAL_REPL = 'EVAL_REPL'; export const PROMPT_AUTOCOMPLETE = 'PROMPT_AUTOCOMPLETE'; @@ -33,6 +35,7 @@ export const PLAYGROUND_EXTERNAL_SELECT = 'PLAYGROUND_EXTERNAL_SELECT '; export const RESET_TESTCASE = 'RESET_TESTCASE'; export const RESET_WORKSPACE = 'RESET_WORKSPACE'; export const SEND_REPL_INPUT_TO_OUTPUT = 'SEND_REPL_INPUT_TO_OUTPUT'; +export const SET_TOKEN_COUNT = 'SET_TOKEN_COUNT'; export const TOGGLE_EDITOR_AUTORUN = 'TOGGLE_EDITOR_AUTORUN'; export const TOGGLE_USING_SUBST = 'TOGGLE_USING_SUBST'; export const TOGGLE_USING_ENV = 'TOGGLE_USING_ENV'; @@ -136,6 +139,9 @@ export type WorkspaceState = { readonly externalLibrary: ExternalLibraryName; readonly replHistory: ReplHistory; readonly replValue: string; + readonly hasTokenCounter: boolean; + readonly tokenCount: integer; + readonly customNotification: string; readonly sharedbConnected: boolean; readonly sideContentHeight?: number; readonly stepLimit: number; diff --git a/src/pages/academy/adminPanel/subcomponents/assessmentConfigPanel/AssessmentConfigPanel.tsx b/src/pages/academy/adminPanel/subcomponents/assessmentConfigPanel/AssessmentConfigPanel.tsx index 5c58c045ad..0e471efac7 100644 --- a/src/pages/academy/adminPanel/subcomponents/assessmentConfigPanel/AssessmentConfigPanel.tsx +++ b/src/pages/academy/adminPanel/subcomponents/assessmentConfigPanel/AssessmentConfigPanel.tsx @@ -49,6 +49,16 @@ const AssessmentConfigPanel: React.FC = props => { gridApi.current?.getDisplayedRowAtIndex(index)?.setDataValue('displayInDashboard', value); }; + const setHasTokenCounter = (index: number, value: boolean) => { + const temp = [...assessmentConfig.current]; + temp[index] = { + ...temp[index], + hasTokenCounter: value + }; + setAssessmentConfig(temp); + gridApi.current?.getDisplayedRowAtIndex(index)?.setDataValue('hasTokenCounter', value); + }; + const setEarlyXp = (index: number, value: number) => { const temp = [...assessmentConfig.current]; temp[index] = { @@ -82,6 +92,7 @@ const AssessmentConfigPanel: React.FC = props => { isManuallyGraded: true, displayInDashboard: true, hoursBeforeEarlyXpDecay: 0, + hasTokenCounter: false, earlySubmissionXp: 0 }); setAssessmentConfig(temp); @@ -126,6 +137,15 @@ const AssessmentConfigPanel: React.FC = props => { field: 'displayInDashboard' } }, + { + headerName: 'Has Token Counter', + field: 'hasTokenCounter', + cellRenderer: BooleanCell, + cellRendererParams: { + setStateHandler: setHasTokenCounter, + field: 'hasTokenCounter' + } + }, { headerName: 'Max Bonus XP', field: 'earlySubmissionXp', diff --git a/src/styles/_workspace.scss b/src/styles/_workspace.scss index 8fdcdb265e..867945d9f0 100755 --- a/src/styles/_workspace.scss +++ b/src/styles/_workspace.scss @@ -2,6 +2,8 @@ $code-color-code: #ced9e0; $code-color-log: #dd8c60; $code-color-result: #ffffff; $code-color-error: #ff4444; +$code-background-color-notification: #f1b80f; +$code-color-notification: #f9f0d7; .workspace { height: 100%; @@ -822,6 +824,14 @@ $code-color-error: #ff4444; .error-output { color: $code-color-error; } + + .notification-output { + color: $code-color-notification; + } + } + + .notification-output-container { + background-color: $code-background-color-notification; } /* flush to align with editor bottom */