1- import React , { FC , useCallback } from "react" ;
1+ import React , { FC , useCallback , useState } from "react" ;
2+
3+ import { SelectButton } from "primereact/selectbutton" ;
4+ import { confirmDialog , ConfirmDialog } from "primereact/confirmdialog" ;
25
36import { CreateExerciseFormType } from "@/types/exercises" ;
47import { createExerciseId } from "@/utils/exercise" ;
@@ -14,7 +17,21 @@ import { validateCommonFields } from "../../utils/validation";
1417
1518import { ParsonsExerciseSettings } from "./ParsonsExerciseSettings" ;
1619import { ParsonsPreview } from "./ParsonsPreview" ;
17- import { ParsonsInstructions , ParsonsLanguageSelector , ParsonsBlocksManager } from "./components" ;
20+ import {
21+ ParsonsInstructions ,
22+ ParsonsLanguageSelector ,
23+ ParsonsBlocksManager ,
24+ ParsonsOptions
25+ } from "./components" ;
26+ import { ParsonsExerciseTour } from "./components/ParsonsExerciseTour" ;
27+ import parsonsStyles from "./components/ParsonsExercise.module.css" ;
28+
29+ export type ParsonsMode = "simple" | "enhanced" ;
30+
31+ const MODE_OPTIONS = [
32+ { label : "Simple" , value : "simple" } ,
33+ { label : "Enhanced" , value : "enhanced" }
34+ ] ;
1835
1936const PARSONS_STEPS = [
2037 { label : "Language" } ,
@@ -39,7 +56,13 @@ const getDefaultFormData = (): ParsonsData => ({
3956 question_type : "parsonsprob" ,
4057 language : "" ,
4158 instructions : "" ,
42- blocks : [ { id : `block-${ Date . now ( ) } ` , content : "" , indent : 0 } ]
59+ blocks : [ { id : `block-${ Date . now ( ) } ` , content : "" , indent : 0 } ] ,
60+ adaptive : true ,
61+ numbered : "left" ,
62+ noindent : false ,
63+ grader : "line" ,
64+ orderMode : "random" ,
65+ customOrder : [ ]
4366} ) ;
4467
4568const generatePreview = ( data : ParsonsData ) : string => {
@@ -48,10 +71,13 @@ const generatePreview = (data: ParsonsData): string => {
4871 blocks : data . blocks || [ ] ,
4972 name : data . name || "parsons_exercise" ,
5073 language : data . language || "python" ,
51- adaptive : true ,
52- numbered : "left" ,
53- noindent : false ,
54- questionLabel : data . name
74+ adaptive : data . adaptive ?? true ,
75+ numbered : data . numbered ?? "left" ,
76+ noindent : data . noindent ?? false ,
77+ questionLabel : data . name ,
78+ grader : data . grader ?? "line" ,
79+ orderMode : data . orderMode ?? "random" ,
80+ customOrder : data . customOrder
5581 } ) ;
5682} ;
5783
@@ -61,10 +87,13 @@ const generateExerciseHtmlSrc = (data: ParsonsData): string => {
6187 instructions : data . instructions || "" ,
6288 blocks : data . blocks || [ ] ,
6389 language : data . language || "python" ,
64- adaptive : true ,
65- numbered : "left" ,
66- noindent : false ,
67- questionLabel : data . name
90+ adaptive : data . adaptive ?? true ,
91+ numbered : data . numbered ?? "left" ,
92+ noindent : data . noindent ?? false ,
93+ questionLabel : data . name ,
94+ grader : data . grader ?? "line" ,
95+ orderMode : data . orderMode ?? "random" ,
96+ customOrder : data . customOrder
6897 } ) ;
6998} ;
7099
@@ -144,6 +173,91 @@ export const ParsonsExercise: FC<ExerciseComponentProps> = ({
144173 [ updateFormData , formData . language , formData . tags ]
145174 ) ;
146175
176+ const handleAddBlock = useCallback ( ( ) => {
177+ const newBlock : ParsonsBlock = {
178+ id : `block-${ Date . now ( ) } ` ,
179+ content : "" ,
180+ indent : 0
181+ } ;
182+ updateFormData ( "blocks" , [ ...( formData . blocks || [ ] ) , newBlock ] ) ;
183+ } , [ updateFormData , formData . blocks ] ) ;
184+
185+ // --- Mode switcher ---
186+ const isEnhancedExercise =
187+ isEdit &&
188+ ( initialData ?. grader === "dag" ||
189+ initialData ?. orderMode === "custom" ||
190+ initialData ?. numbered === "right" ||
191+ initialData ?. numbered === "none" ||
192+ initialData ?. noindent === true ) ;
193+
194+ const [ mode , setMode ] = useState < ParsonsMode > ( isEnhancedExercise ? "enhanced" : "simple" ) ;
195+
196+ const directSetMode = useCallback ( ( newMode : ParsonsMode ) => {
197+ setMode ( newMode ) ;
198+ } , [ ] ) ;
199+
200+ const tourButton = (
201+ < ParsonsExerciseTour
202+ mode = { mode }
203+ formData = { formData }
204+ onModeChange = { directSetMode }
205+ updateFormData = { updateFormData as ( key : string , value : any ) => void }
206+ />
207+ ) ;
208+
209+ const handleModeChange = useCallback (
210+ ( newMode : ParsonsMode ) => {
211+ if ( newMode === mode || ! newMode ) return ;
212+
213+ if ( newMode === "simple" ) {
214+ // Enhanced → Simple: confirm and reset
215+ confirmDialog ( {
216+ message :
217+ "Switching to Simple Mode will reset Grader, Order, Line Numbers, and No Indent to their default values. Block dropdown settings (DAG tags, dependencies, custom order) will be cleared. Continue?" ,
218+ header : "Switch to Simple Mode" ,
219+ icon : "pi pi-exclamation-triangle" ,
220+ acceptClassName : "p-button-warning" ,
221+ accept : ( ) => {
222+ // Reset locked fields
223+ updateFormData ( "grader" , "line" ) ;
224+ updateFormData ( "orderMode" , "random" ) ;
225+ updateFormData ( "numbered" , "left" ) ;
226+ updateFormData ( "noindent" , false ) ;
227+ updateFormData ( "adaptive" , true ) ;
228+ updateFormData ( "customOrder" , [ ] ) ;
229+ // Clear DAG / order block fields
230+ const clearedBlocks = ( formData . blocks || [ ] ) . map ( ( block ) => ( {
231+ ...block ,
232+ tag : undefined ,
233+ depends : undefined ,
234+ displayOrder : undefined
235+ } ) ) ;
236+ updateFormData ( "blocks" , clearedBlocks ) ;
237+ setMode ( "simple" ) ;
238+ }
239+ } ) ;
240+ } else {
241+ // Simple → Enhanced: no data loss, just switch
242+ setMode ( "enhanced" ) ;
243+ }
244+ } ,
245+ [ mode , updateFormData , formData . blocks ]
246+ ) ;
247+
248+ const modeSwitcher = (
249+ < div className = { parsonsStyles . modeSwitcher } data-tour = "mode-switcher" >
250+ < span className = { parsonsStyles . modeSwitcherLabel } > Mode</ span >
251+ < SelectButton
252+ value = { mode }
253+ options = { MODE_OPTIONS }
254+ onChange = { ( e ) => handleModeChange ( e . value ) }
255+ className = { parsonsStyles . modeSwitcherButton }
256+ allowEmpty = { false }
257+ />
258+ </ div >
259+ ) ;
260+
147261 const renderStepContent = ( ) => {
148262 switch ( activeStep ) {
149263 case 0 :
@@ -164,11 +278,46 @@ export const ParsonsExercise: FC<ExerciseComponentProps> = ({
164278
165279 case 2 :
166280 return (
167- < ParsonsBlocksManager
168- blocks = { formData . blocks || [ ] }
169- onChange = { ( blocks : ParsonsBlock [ ] ) => updateFormData ( "blocks" , blocks ) }
170- language = { formData . language || "python" }
171- />
281+ < div className = "flex flex-column gap-4" >
282+ < ParsonsOptions
283+ adaptive = { formData . adaptive ?? true }
284+ numbered = { formData . numbered ?? "left" }
285+ noindent = { formData . noindent ?? false }
286+ grader = { formData . grader ?? "line" }
287+ orderMode = { formData . orderMode ?? "random" }
288+ mode = { mode }
289+ onAdaptiveChange = { ( value : boolean ) => updateFormData ( "adaptive" , value ) }
290+ onNumberedChange = { ( value : "left" | "right" | "none" ) =>
291+ updateFormData ( "numbered" , value )
292+ }
293+ onNoindentChange = { ( value : boolean ) => updateFormData ( "noindent" , value ) }
294+ onGraderChange = { ( value : "line" | "dag" ) => {
295+ updateFormData ( "grader" , value ) ;
296+ if ( value === "dag" ) {
297+ updateFormData ( "adaptive" , false ) ;
298+ // Auto-assign tags to blocks that don't have them
299+ const updatedBlocks = ( formData . blocks || [ ] ) . map ( ( block , idx ) => {
300+ if ( ! block . tag && ! block . isDistractor && ! block . groupId ) {
301+ return { ...block , tag : String ( idx ) } ;
302+ }
303+ return block ;
304+ } ) ;
305+ updateFormData ( "blocks" , updatedBlocks ) ;
306+ }
307+ } }
308+ onOrderModeChange = { ( value : "random" | "custom" ) => updateFormData ( "orderMode" , value ) }
309+ onAddBlock = { handleAddBlock }
310+ tourButton = { tourButton }
311+ />
312+ < ParsonsBlocksManager
313+ blocks = { formData . blocks || [ ] }
314+ onChange = { ( blocks : ParsonsBlock [ ] ) => updateFormData ( "blocks" , blocks ) }
315+ language = { formData . language || "python" }
316+ grader = { formData . grader ?? "line" }
317+ orderMode = { formData . orderMode ?? "random" }
318+ mode = { mode }
319+ />
320+ </ div >
172321 ) ;
173322
174323 case 3 :
@@ -181,10 +330,13 @@ export const ParsonsExercise: FC<ExerciseComponentProps> = ({
181330 blocks = { formData . blocks || [ ] }
182331 language = { formData . language || "python" }
183332 name = { formData . name || "" }
184- adaptive = { true }
185- numbered = "left"
186- noindent = { false }
333+ adaptive = { formData . adaptive ?? true }
334+ numbered = { formData . numbered ?? "left" }
335+ noindent = { formData . noindent ?? false }
187336 questionLabel = { formData . name }
337+ grader = { formData . grader ?? "line" }
338+ orderMode = { formData . orderMode ?? "random" }
339+ customOrder = { formData . customOrder }
188340 />
189341 ) ;
190342
@@ -194,23 +346,27 @@ export const ParsonsExercise: FC<ExerciseComponentProps> = ({
194346 } ;
195347
196348 return (
197- < ExerciseLayout
198- title = "Parsons Exercise"
199- exerciseType = "parsonsprob"
200- isEdit = { isEdit }
201- steps = { PARSONS_STEPS }
202- activeStep = { activeStep }
203- isCurrentStepValid = { isCurrentStepValid }
204- isSaving = { isSaving }
205- stepsValidity = { stepsValidity }
206- onCancel = { onCancel }
207- onBack = { goToPrevStep }
208- onNext = { handleNext }
209- onSave = { handleSave }
210- onStepSelect = { handleStepSelect }
211- validation = { validation }
212- >
213- { renderStepContent ( ) }
214- </ ExerciseLayout >
349+ < >
350+ < ConfirmDialog />
351+ < ExerciseLayout
352+ title = "Parsons Exercise"
353+ exerciseType = "parsonsprob"
354+ isEdit = { isEdit }
355+ steps = { PARSONS_STEPS }
356+ activeStep = { activeStep }
357+ isCurrentStepValid = { isCurrentStepValid }
358+ isSaving = { isSaving }
359+ stepsValidity = { stepsValidity }
360+ onCancel = { onCancel }
361+ onBack = { goToPrevStep }
362+ onNext = { handleNext }
363+ onSave = { handleSave }
364+ onStepSelect = { handleStepSelect }
365+ validation = { validation }
366+ headerExtra = { modeSwitcher }
367+ >
368+ { renderStepContent ( ) }
369+ </ ExerciseLayout >
370+ </ >
215371 ) ;
216372} ;
0 commit comments