import React , { forwardRef , useImperativeHandle , useState } from "react" ; import Blockly from "blockly/core" ; import { Paper , Button , makeStyles } from "@material-ui/core" ; import { PlayArrow , FileCopy , Clear } from "@material-ui/icons" ; import { run as runInContext , highlightBlock } from "../runner/runMain" ; import { dv_reset } from "src/blocks/DataView_block" ; import NewConsole , { ConsoleLine } from "./NewConsole" ; import { useSave } from "src/components/AutosaverProvider" ; import { RunError } from "src/runner/shared" ; type RunnerGetState = ConsoleLine [ ] | undefined ; type RunnerSetState = { ( newState ) : void ; } ; type RunnerResetState = { ( ) : void ; } ; interface IRunnerProps { getCode : { ( ) : string ; } ; } const useStyles = makeStyles ( ( theme ) => ( { runnerContainer : { display : "flex" , flexDirection : "column" , "& .react-console-message-run-start, .react-console-message-run-end, .react-console-message-exception" : { fontWeight : "bolder" , fontSize : "1.1em" } , "& .react-console-message-run-start, .react-console-message-run-end, .react-console-message-input-log" : { color : "#595959 !important" } , "& .react-console-message-exception" : { color : "red !important" } , "& .react-console-message-exception-message" : { fontWeight : "bolder" } , "& .react-console-message-input-log" : { fontWeight : "bold" } } , runnerControls : { margin : "0.5rem" } , floatRight : { float : "right" } } ) ) ; export interface RunnerRef { state : RunnerGetState ; setState : RunnerSetState ; resetState : RunnerResetState ; } function Runner ( { getCode } , ref ) { const styles = useStyles ( ) ; const [ consoleState , setConsoleState ] = useState < ConsoleLine [ ] > ( [ ] ) ; const [ consoleCanType , setConsoleCanType ] = useState ( false ) ; const [ userInputCallback , setUserInputCallback ] = useState < { ( text ) : void ; } | undefined > ( ) ; const save = useSave ( ) ; const logMessage = ( text , className ? ) => { consoleState . push ( { text , className } ) ; setConsoleState ( [ ... consoleState ] ) ; save ( ) ; } ; useImperativeHandle < unknown , RunnerRef > ( ref , ( ) => ( { state : consoleState , setState : ( newState ) => { setConsoleState ( newState ) ; } , resetState : ( ) => { setConsoleState ( [ ] ) ; } } ) , [ consoleState , setConsoleState ] ) ; async function run ( ) { setConsoleCanType ( false ) ; logMessage ( "Run started at " + new Date ( ) . toLocaleTimeString ( ) , "run-start" ) ; const source = getCode ( ) ; dv_reset ( ) ; const runResult = await runInContext ( source , { runnerConsole : logMessage , runnerConsoleGetInput } ) ; if ( runResult ) { logMessage ( "Error" , "exception" ) ; let text = undefined ; if ( runResult . blockId !== undefined ) { const block = Blockly . getMainWorkspace ( ) . getBlockById ( runResult . blockId ) ; if ( block !== null ) { const blockType = block . type ; text = block . inputList . map ( ( input ) => input . fieldRow . map ( ( field ) => field instanceof Blockly . FieldLabel ? field . value_ : " _ " ) . join ( "" ) ) . join ( " _ " ) + ( block . inputList [ block . inputList . length - 1 ] . connection ? " _" : "" ) ; if ( ! text ) { text = blockType . replace ( "_" , " " ) ; } } } if ( ! text ) { logMessage ( "Block type: could not be identified" , "exception-details" ) ; logMessage ( "This may mean that you did not connect a required input to a block" , "exception-details" ) ; } else { logMessage ( "Block type: " + text , "exception-details" ) ; } logMessage ( `${ runResult . exception . name }: ${ runResult . exception . message }` , "exception-details" ) ; if ( text ) { logMessage ( "The block that caused the problem has been highlighted" , "exception-details" ) ; } console . log ( "%cException in generated code" , "color: red" ) ; console . log ( runResult . exception ) ; console . log ( "Generated code:" ) ; console . log ( source ) ; } else { highlightBlock ( "" ) ; } logMessage ( "Run ended at " + new Date ( ) . toLocaleTimeString ( ) , "run-end" ) ; } function copyLog ( ) { let output = "" ; consoleState . forEach ( ( line ) => { if ( line . text . length > 0 ) { output += line . text + "\n" ; } } ) ; if ( ! navigator . clipboard ) { logMessage ( "Your browser does not support the Clipboard API." , "exception-details" ) ; return ; } navigator . clipboard . writeText ( output ) . then ( ( ) => { } , ( err ) => { logMessage ( "Error copying text: " + err , "exception-details" ) ; } ) ; } function runnerConsoleGetInput ( ) { return new Promise ( ( resolve ) => { setConsoleCanType ( true ) ; setUserInputCallback ( ( ) => ( text ) => { resolve ( text ) ; logMessage ( "> " + text , "input-log" ) ; setConsoleCanType ( false ) ; setUserInputCallback ( undefined ) ; } ) ; } ) ; } return ( < Paper className = { styles . runnerContainer } > < div key = { "runnercontrols" } className = { styles . runnerControls } > < Button variant = "contained" color = "primary" startIcon = { < PlayArrow / > } onClick = { run } > Run < /Button> < div className = { styles . floatRight } > < Button startIcon = { < FileCopy />} onClick={copyLog}> Copy log < /Button> < Button startIcon = { < Clear / > } onClick = { ( ) => { setConsoleState ( [ ] ) ; save ( ) ; } } > Clear < /Button> < /div> < /div> < NewConsole data = { consoleState } canType = { consoleCanType } onSubmit = { userInputCallback ?? ( ( ) => { } ) } welcomeMessage = "The output of your program will be displayed here" promptLabel = "> " / > < /Paper>); } const RefRunner = forwardRef ( Runner ) ; export default RefRunner ;