@@ -3,9 +3,50 @@ import {subGoal, stepInfo, allStages, steps, stepSubgoalMap, vfg, textContent,
33 getAllStages , getSteps , getStepInfo , getSubGoal , getStepSubgoalMap } from './dataUtils' ;
44import Button from '@material-ui/core/Button' ;
55import styles from './index.less' ;
6- import Screen , { ControlPanel , StepScreen , GoalScreen } from "./screenComponents" ;
6+ import Screen , { ControlPanel , StepScreen , GoalScreen , SplitButton } from "./screenComponents" ;
77
88
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+
950class PageFour extends React . Component {
1051
1152 constructor ( props ) {
@@ -30,12 +71,21 @@ class PageFour extends React.Component {
3071 pauseButtonColor : 'default' ,
3172 canvasWidth : 720 ,
3273 canvasHeight : 470 ,
74+ radioOption : 'all' ,
75+ currentDialogType : null ,
76+ isLoading : false ,
77+ qualityOption : 'medium' ,
3378 }
3479
3580 // Every function that interfaces with UI and data used
3681 // in this class needs to bind like this:
3782 this . handleOnClick = this . handleOnClick . bind ( this ) ;
3883 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 ) ;
3989 }
4090
4191
@@ -393,12 +443,86 @@ class PageFour extends React.Component {
393443 }
394444
395445
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+
396520
397521 handleSpeedControllor = ( value ) => {
398522 this . setState ( {
399523 playSpeed : value
400- } )
401- }
524+ } ) ;
525+ } ;
402526
403527
404528 /**
@@ -413,52 +537,191 @@ class PageFour extends React.Component {
413537 }
414538
415539
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+
416591
417592 render ( ) {
418593 // Get all sprites
419594 let sprites = this . state . drawSprites ;
420595 // 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 ) ;
422597
423598 return (
424- < div className = { styles . container } ref = { ( ref ) => this . refDom = ref } >
599+ < div className = { styles . container } ref = { ( ref ) => this . refDom = ref } >
425600 < 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 } />
427602 </ div >
428603 < 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 } />
430605 < div className = { styles . btn_box } >
431606 < 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 >
442618 </ div >
443619 </ div >
444620 </ div >
445-
621+
446622 < 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 ( ) } } >
449625 Show the Goal
450626 </ Button >
451627
452- < Button variant = "contained" color = "primary" size = "small" onClick = { ( ) => { this . handleExportClick ( ) } } >
628+ < Button variant = "contained" color = "primary" size = "small" onClick = { this . handleMenuOpen } >
453629 Export
454630 </ 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 >
455719 </ div >
456720 < 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 } />
458722 </ div >
459723 </ div >
460- ) ;
724+ ) ;
461725 }
462- }
463-
464- export default PageFour ;
726+ }
727+ export default PageFour ;
0 commit comments