1- import { Slider } from '@blueprintjs/core' ;
1+ import { EditableText , Slider } from '@blueprintjs/core' ;
22import type { CurveDrawn } from '@sourceacademy/bundle-curve/curves_webgl' ;
33import PlayButton from '@sourceacademy/modules-lib/tabs/PlayButton' ;
44import WebGLCanvas from '@sourceacademy/modules-lib/tabs/WebGLCanvas' ;
55import { BP_TAB_BUTTON_MARGIN , BP_TEXT_MARGIN , CANVAS_MAX_WIDTH , } from '@sourceacademy/modules-lib/tabs/css_constants' ;
6+ import { useAnimation } from '@sourceacademy/modules-lib/tabs/useAnimation' ;
67import { degreesToRadians } from '@sourceacademy/modules-lib/utilities' ;
8+ import { clamp } from 'lodash' ;
79import React from 'react' ;
810
11+ export default function Canvas3DCurve ( { curve } : Props ) {
12+ const canvasRef = React . useRef < HTMLCanvasElement | null > ( null ) ;
13+ const [ angleText , setAngleText ] = React . useState < string | null > ( null ) ;
14+ const [ isEditing , setIsEditing ] = React . useState ( false ) ;
15+
16+ const {
17+ isPlaying : isRotating ,
18+ start,
19+ stop,
20+ changeTimestamp : setDisplayAngle ,
21+ timestamp : displayAngle ,
22+ setCanvas,
23+ } = useAnimation ( {
24+ animationDuration : 7200 ,
25+ autoLoop : true ,
26+ callback ( angle ) {
27+ const angleInRadians = degreesToRadians ( angle / 20 ) ;
28+ curve . redraw ( angleInRadians ) ;
29+ }
30+ } ) ;
31+
32+ React . useEffect ( ( ) => {
33+ if ( canvasRef . current ) {
34+ curve . init ( canvasRef . current ) ;
35+ setCanvas ( canvasRef . current ) ;
36+ }
37+ } , [ curve , canvasRef . current ] ) ;
38+
39+ return < div
40+ style = { { width : '100%' } }
41+ >
42+ < div
43+ style = { {
44+ display : 'flex' ,
45+ justifyContent : 'center'
46+ } }
47+ >
48+ < div
49+ style = { {
50+ display : 'flex' ,
51+ alignItems : 'center' ,
52+ gap : BP_TAB_BUTTON_MARGIN ,
53+
54+ width : '100%' ,
55+ maxWidth : CANVAS_MAX_WIDTH ,
56+
57+ paddingTop : BP_TEXT_MARGIN ,
58+ paddingBottom : BP_TEXT_MARGIN
59+ } }
60+ >
61+ < PlayButton
62+ isPlaying = { isRotating }
63+ onClick = { ( ) => {
64+ if ( isRotating ) stop ( ) ;
65+ else start ( ) ;
66+ } }
67+ />
68+ < Slider
69+ value = { displayAngle / 20 }
70+ min = { 0 }
71+ max = { 360 }
72+ labelRenderer = { false }
73+ onChange = { newValue => {
74+ stop ( ) ;
75+ setDisplayAngle ( newValue * 20 ) ;
76+ } }
77+ />
78+ < EditableText
79+ customInputAttributes = { {
80+ style : { height : '100%' }
81+ } }
82+ isEditing = { isEditing }
83+ onEdit = { ( ) => setIsEditing ( true ) }
84+ type = "number"
85+ value = { angleText ?? Math . round ( displayAngle / 20 ) . toString ( ) }
86+ disabled = { isRotating }
87+ onChange = { value => {
88+ setAngleText ( value ) ;
89+ if ( isEditing ) return ;
90+
91+ const angle = parseFloat ( value ) ;
92+ if ( ! Number . isNaN ( angle ) ) {
93+ setDisplayAngle ( clamp ( angle , 0 , 360 ) * 20 ) ;
94+ }
95+ setAngleText ( null ) ;
96+ } }
97+ onConfirm = { value => {
98+ const angle = parseFloat ( value ) ;
99+ setDisplayAngle ( clamp ( angle , 0 , 360 ) * 20 ) ;
100+ setAngleText ( null ) ;
101+ setIsEditing ( false ) ;
102+ } }
103+ onCancel = { ( ) => setIsEditing ( false ) }
104+ />
105+ </ div >
106+ </ div >
107+ < div
108+ style = { {
109+ display : 'flex' ,
110+ justifyContent : 'center'
111+ } }
112+ >
113+ < WebGLCanvas ref = { canvasRef } />
114+ </ div >
115+ </ div > ;
116+ }
117+
9118type State = {
10119 /**
11120 * Slider component reflects this value. This value is also passed in as
@@ -31,7 +140,7 @@ type Props = {
31140 *
32141 * Uses WebGLCanvas internally.
33142 */
34- export default class Canvas3dCurve extends React . Component < Props , State > {
143+ export class Canvas3dCurve extends React . Component < Props , State > {
35144 private canvas : HTMLCanvasElement | null ;
36145
37146 constructor ( props ) {
0 commit comments