@@ -3,9 +3,50 @@ import {subGoal, stepInfo, allStages, steps, stepSubgoalMap, vfg, textContent,
3
3
getAllStages , getSteps , getStepInfo , getSubGoal , getStepSubgoalMap } from './dataUtils' ;
4
4
import Button from '@material-ui/core/Button' ;
5
5
import styles from './index.less' ;
6
- import Screen , { ControlPanel , StepScreen , GoalScreen } from "./screenComponents" ;
6
+ import Screen , { ControlPanel , StepScreen , GoalScreen , SplitButton } from "./screenComponents" ;
7
7
8
8
9
+ import Dialog from '@mui/material/Dialog' ;
10
+ import DialogTitle from '@mui/material/DialogTitle' ;
11
+ import DialogContent from '@mui/material/DialogContent' ;
12
+ import TextField from '@mui/material/TextField' ;
13
+ import DialogActions from '@mui/material/DialogActions' ;
14
+
15
+ import Menu from '@mui/material/Menu' ;
16
+ import MenuItem from '@mui/material/MenuItem' ;
17
+
18
+ import {
19
+ FormControl ,
20
+ RadioGroup ,
21
+ FormControlLabel ,
22
+ Radio
23
+ } from '@material-ui/core' ;
24
+
25
+ import CircularProgress from "@material-ui/core/CircularProgress" ;
26
+
27
+ import Select from '@mui/material/Select' ;
28
+
29
+ import { InputLabel } from '@material-ui/core' ;
30
+
31
+ class mediaDataLabel {
32
+
33
+ constructor ( fileType = 'vfg' , startStep = 0 , stopStep = 1 , quality = "high" ) {
34
+ this . fileType = fileType ;
35
+ this . startStep = startStep ;
36
+ this . stopStep = stopStep ;
37
+ this . quality = quality ;
38
+ }
39
+ bodyContent ( ) {
40
+ const bodyContent = {
41
+ 'fileType' : this . fileType ,
42
+ 'startStep' : this . startStep ,
43
+ 'stopStep' : this . stopStep ,
44
+ 'quality' : this . quality
45
+ }
46
+ return bodyContent ;
47
+ }
48
+ }
49
+
9
50
class PageFour extends React . Component {
10
51
11
52
constructor ( props ) {
@@ -30,12 +71,21 @@ class PageFour extends React.Component {
30
71
pauseButtonColor : 'default' ,
31
72
canvasWidth : 720 ,
32
73
canvasHeight : 470 ,
74
+ radioOption : 'all' ,
75
+ currentDialogType : null ,
76
+ isLoading : false ,
77
+ qualityOption : 'medium' ,
33
78
}
34
79
35
80
// Every function that interfaces with UI and data used
36
81
// in this class needs to bind like this:
37
82
this . handleOnClick = this . handleOnClick . bind ( this ) ;
38
83
this . updateWindowDimensions = this . updateWindowDimensions . bind ( this ) ;
84
+ this . handleMenuOpen = this . handleMenuOpen . bind ( this ) ;
85
+ this . handleMenuClose = this . handleMenuClose . bind ( this ) ;
86
+ this . handleOpenDialog = this . handleOpenDialog . bind ( this ) ;
87
+ this . handleCloseDialog = this . handleCloseDialog . bind ( this ) ;
88
+ this . handleNumberChange = this . handleNumberChange . bind ( this ) ;
39
89
}
40
90
41
91
@@ -393,12 +443,86 @@ class PageFour extends React.Component {
393
443
}
394
444
395
445
446
+ async sendMediaRequestAndDownload ( fileType , startStep , stopStep , qualityOption ) {
447
+ try {
448
+ const vfgText = JSON . stringify ( vfg ) ;
449
+ //console.log(vfgText);
450
+ var label = new mediaDataLabel ( ) ;
451
+ // assign any parameters here. otherwise, default will be used.
452
+ label . fileType = fileType ;
453
+ switch ( fileType ) {
454
+ case "mp4" :
455
+ label . startStep = startStep ;
456
+ label . stopStep = stopStep ;
457
+ label . quality = qualityOption ;
458
+ break ;
459
+ case "png" :
460
+ label . startStep = startStep ;
461
+ label . stopStep = stopStep ;
462
+ break ;
463
+ case "gif" :
464
+ label . startStep = startStep ;
465
+ label . stopStep = stopStep ;
466
+ label . quality = qualityOption ;
467
+ break ;
468
+ }
469
+
470
+ const requestData = {
471
+ method : 'POST' ,
472
+ body : JSON . stringify ( {
473
+ 'vfg' : vfgText ,
474
+ 'fileType' : fileType ,
475
+ 'params' : label . bodyContent ( )
476
+ } )
477
+ } ;
478
+
479
+ // For local testing, uncomment the next line and comment the following line.
480
+ //const response = await fetch("http://localhost:8000/downloadVisualisation", requestData);
481
+ const response = await fetch ( "https://planimation.planning.domains/downloadVisualisation" , requestData ) ;
482
+
483
+ if ( response . ok ) {
484
+ console . log ( "Response was OK" ) ;
485
+ const blob = await response . blob ( ) ;
486
+ const url = window . URL . createObjectURL ( blob ) ;
487
+ const a = document . createElement ( 'a' ) ;
488
+ a . style . display = 'none' ;
489
+ a . href = url ;
490
+ if ( fileType == "png" ) {
491
+ fileType = "zip" ;
492
+ }
493
+ a . download = 'planimation.' + fileType ;
494
+ document . body . appendChild ( a ) ;
495
+ a . click ( ) ;
496
+ window . URL . revokeObjectURL ( url ) ;
497
+ document . body . removeChild ( a ) ;
498
+ } else {
499
+ console . error ( "Failed to download the file." ) ;
500
+ }
501
+ } catch ( error ) {
502
+ console . error ( "Error:" , error ) ;
503
+ }
504
+ }
505
+
506
+
507
+ // this function is for testing @Wenxuan
508
+ async handleMediaButtonClick ( fileType ) {
509
+ try {
510
+
511
+ this . sendMediaRequestAndDownload ( fileType ) ;
512
+
513
+ } catch ( err ) {
514
+ console . error ( "There was an error: " , err ) ;
515
+ }
516
+
517
+
518
+ }
519
+
396
520
397
521
handleSpeedControllor = ( value ) => {
398
522
this . setState ( {
399
523
playSpeed : value
400
- } )
401
- }
524
+ } ) ;
525
+ } ;
402
526
403
527
404
528
/**
@@ -413,52 +537,191 @@ class PageFour extends React.Component {
413
537
}
414
538
415
539
540
+ handleMenuOpen = ( event ) => {
541
+ this . setState ( { anchorEl : event . currentTarget } ) ;
542
+ } ;
543
+
544
+ handleMenuClose = ( ) => {
545
+ this . setState ( { anchorEl : null } ) ;
546
+ } ;
547
+
548
+ handleOpenDialog = ( type ) => {
549
+ this . setState ( {
550
+ isModalOpen : true ,
551
+ currentDialogType : type
552
+ } ) ;
553
+ }
554
+
555
+
556
+ handleCloseDialog = ( ) => {
557
+ this . setState ( { isModalOpen : false } ) ;
558
+ }
559
+
560
+ handleNumberChange = ( e , numberIndex ) => {
561
+ this . setState ( { [ numberIndex ] : e . target . value } ) ;
562
+ }
563
+
564
+ handleRadioChange = ( event ) => {
565
+ this . setState ( {
566
+ radioOption : event . target . value
567
+ } ) ;
568
+ }
569
+
570
+
571
+ handleDownload = async ( ) => {
572
+ this . setState ( { isloading : true } ) ;
573
+ if ( this . state . radioOption === 'all' ) {
574
+ await this . sendMediaRequestAndDownload ( this . state . currentDialogType , 0 , 9999 , this . state . qualityOption ) ;
575
+ } else {
576
+ const { number1, number2 } = this . state ;
577
+ await this . sendMediaRequestAndDownload ( this . state . currentDialogType , number1 , number2 , this . state . qualityOption ) ;
578
+ }
579
+ this . handleCloseDialog ( ) ;
580
+ setTimeout ( ( ) => {
581
+ this . setState ( { isloading : false } ) ;
582
+ } , 2000 ) ;
583
+ }
584
+
585
+ handleQualityChange = ( event ) => {
586
+ this . setState ( { qualityOption : event . target . value } ) ;
587
+ } ;
588
+
589
+
590
+
416
591
417
592
render ( ) {
418
593
// Get all sprites
419
594
let sprites = this . state . drawSprites ;
420
595
// Sort sprites by their depth
421
- sprites && sprites . sort ( ( itemA , itemB ) => itemA . depth - itemB . depth )
596
+ sprites && sprites . sort ( ( itemA , itemB ) => itemA . depth - itemB . depth ) ;
422
597
423
598
return (
424
- < div className = { styles . container } ref = { ( ref ) => this . refDom = ref } >
599
+ < div className = { styles . container } ref = { ( ref ) => this . refDom = ref } >
425
600
< div className = { styles . left } >
426
- < StepScreen stepInfoIndex = { this . state . stepInfoIndex } stepItem = { this . stepItem } stepInfo = { stepInfo } onStepClick = { this . handleStepsClick } />
601
+ < StepScreen stepInfoIndex = { this . state . stepInfoIndex } stepItem = { this . stepItem } stepInfo = { stepInfo } onStepClick = { this . handleStepsClick } />
427
602
</ div >
428
603
< div className = { styles . middle } >
429
- < Screen canvasWidth = { this . state . canvasWidth } canvasHeight = { this . state . canvasHeight } sprites = { this . state . drawSprites } vfg = { vfg } />
604
+ < Screen canvasWidth = { this . state . canvasWidth } canvasHeight = { this . state . canvasHeight } sprites = { this . state . drawSprites } vfg = { vfg } />
430
605
< div className = { styles . btn_box } >
431
606
< div >
432
- < ControlPanel
433
- playButtonColor = { this . state . playButtonColor }
434
- pauseButtonColor = { this . state . pauseButtonColor }
435
- stepInfoIndex = { this . state . stepInfoIndex }
436
- onPreviousClick = { this . handlePreviousClick }
437
- onStartClick = { this . handleStartClick }
438
- onPauseClick = { this . handlePauseClick }
439
- onNextClick = { this . handleNextClick }
440
- onResetClick = { this . handleResetClick }
441
- onSpeedControllor = { this . handleSpeedControllor } > </ ControlPanel >
607
+ < ControlPanel
608
+ playButtonColor = { this . state . playButtonColor }
609
+ pauseButtonColor = { this . state . pauseButtonColor }
610
+ stepInfoIndex = { this . state . stepInfoIndex }
611
+ onPreviousClick = { this . handlePreviousClick }
612
+ onStartClick = { this . handleStartClick }
613
+ onPauseClick = { this . handlePauseClick }
614
+ onNextClick = { this . handleNextClick }
615
+ onResetClick = { this . handleResetClick }
616
+ onSpeedControllor = { this . handleSpeedControllor } >
617
+ </ ControlPanel >
442
618
</ div >
443
619
</ div >
444
620
</ div >
445
-
621
+
446
622
< div className = { styles . right } >
447
- < div style = { { marginTop :'5px' , marginBottom :'5px' , width : '220px' } } >
448
- < Button variant = "contained" color = "primary" size = "small" onClick = { ( ) => { this . handleShowFinalGoalClick ( ) } } >
623
+ < div style = { { marginTop : '5px' , marginBottom : '5px' , width : '220px' } } >
624
+ < Button variant = "contained" color = "primary" size = "small" onClick = { ( ) => { this . handleShowFinalGoalClick ( ) } } >
449
625
Show the Goal
450
626
</ Button >
451
627
452
- < Button variant = "contained" color = "primary" size = "small" onClick = { ( ) => { this . handleExportClick ( ) } } >
628
+ < Button variant = "contained" color = "primary" size = "small" onClick = { this . handleMenuOpen } >
453
629
Export
454
630
</ Button >
631
+ { this . state . isloading && < CircularProgress size = { 24 } /> }
632
+ < Menu
633
+ anchorEl = { this . state . anchorEl }
634
+ open = { Boolean ( this . state . anchorEl ) }
635
+ onClose = { this . handleMenuClose }
636
+ >
637
+ < MenuItem onClick = { ( ) => { this . handleExportClick ( ) ; this . handleMenuClose ( ) ; } } >
638
+ Export .vfg
639
+ </ MenuItem >
640
+ < MenuItem onClick = { ( ) => { this . handleOpenDialog ( "png" ) ; this . handleMenuClose ( ) ; } } >
641
+ Export .png
642
+ </ MenuItem >
643
+ < MenuItem onClick = { ( ) => { this . handleOpenDialog ( "gif" ) ; this . handleMenuClose ( ) ; } } >
644
+ Export .gif
645
+ </ MenuItem >
646
+ < MenuItem onClick = { ( ) => { this . handleOpenDialog ( "mp4" ) ; this . handleMenuClose ( ) ; } } >
647
+ Export .mp4
648
+ </ MenuItem >
649
+ </ Menu >
650
+ < Dialog open = { this . state . isModalOpen } onClose = { this . handleCloseDialog } >
651
+ < DialogTitle > Download as { this . state . currentDialogType } </ DialogTitle >
652
+ < DialogContent >
653
+ < FormControl component = "fieldset" >
654
+ < RadioGroup
655
+ value = { this . state . radioOption }
656
+ onChange = { this . handleRadioChange }
657
+ >
658
+ < FormControlLabel
659
+ value = "all"
660
+ control = { < Radio /> }
661
+ label = "Download All"
662
+ />
663
+ < FormControlLabel
664
+ value = "range"
665
+ control = { < Radio /> }
666
+ label = "Specify range"
667
+ />
668
+ </ RadioGroup >
669
+ </ FormControl >
670
+ { /* No need for quality option for PNGs */ }
671
+
672
+
673
+ { this . state . radioOption === 'range' && (
674
+ < >
675
+ < div > < small > Please enter a step range within 0 and { Number ( steps . length ) - 1 } .</ small > </ div >
676
+ < TextField
677
+ autoFocus
678
+ margin = "dense"
679
+ id = "number1"
680
+ label = "start"
681
+ type = "number"
682
+ fullWidth
683
+ value = { this . state . number1 }
684
+ onChange = { ( e ) => this . handleNumberChange ( e , 'number1' ) }
685
+ />
686
+ < TextField
687
+ margin = "dense"
688
+ id = "number2"
689
+ label = "end"
690
+ type = "number"
691
+ fullWidth
692
+ value = { this . state . number2 }
693
+ onChange = { ( e ) => this . handleNumberChange ( e , 'number2' ) }
694
+ />
695
+ </ >
696
+ ) }
697
+ { this . state . currentDialogType !== "png" && (
698
+ < >
699
+ < FormControl fullWidth >
700
+ < InputLabel id = "quality-label" > Quality</ InputLabel >
701
+ < Select
702
+ labelId = "quality-label"
703
+ value = { this . state . qualityOption }
704
+ onChange = { this . handleQualityChange }
705
+ >
706
+ < MenuItem value = "low" > Low</ MenuItem >
707
+ < MenuItem value = "medium" > Medium</ MenuItem >
708
+ < MenuItem value = "high" > High</ MenuItem >
709
+ </ Select >
710
+ </ FormControl >
711
+ </ >
712
+ ) }
713
+ </ DialogContent >
714
+ < DialogActions >
715
+ < Button onClick = { this . handleCloseDialog } color = "primary" > Cancel</ Button >
716
+ < Button onClick = { this . handleDownload } color = "primary" > Confirm</ Button >
717
+ </ DialogActions >
718
+ </ Dialog >
455
719
</ div >
456
720
< GoalScreen sprites = { sprites } subGoal = { subGoal } selectedSubGoals = { this . state . selectedSubGoals }
457
- showKey = { this . state . showKey } onSubItemClick = { this . handleSubItemClick } onSubgoalStepItemClick = { this . handleSubgoalStepItemClick } />
721
+ showKey = { this . state . showKey } onSubItemClick = { this . handleSubItemClick } onSubgoalStepItemClick = { this . handleSubgoalStepItemClick } />
458
722
</ div >
459
723
</ div >
460
- ) ;
724
+ ) ;
461
725
}
462
- }
463
-
464
- export default PageFour ;
726
+ }
727
+ export default PageFour ;
0 commit comments