From ab180083cf36e96bd2bdf30f8cd1254aefbd9bf3 Mon Sep 17 00:00:00 2001 From: alsonleej Date: Thu, 13 Mar 2025 03:25:19 +0800 Subject: [PATCH 01/11] spread animation --- .../cseMachine/CseMachineAnimation.tsx | 35 ++++ .../ArraySpreadAnimation.tsx | 185 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx diff --git a/src/features/cseMachine/CseMachineAnimation.tsx b/src/features/cseMachine/CseMachineAnimation.tsx index 9249c7bca4..9cf20e0134 100644 --- a/src/features/cseMachine/CseMachineAnimation.tsx +++ b/src/features/cseMachine/CseMachineAnimation.tsx @@ -20,6 +20,7 @@ import { InstructionApplicationAnimation } from './animationComponents/Instructi import { LookupAnimation } from './animationComponents/LookupAnimation'; import { PopAnimation } from './animationComponents/PopAnimation'; import { UnaryOperationAnimation } from './animationComponents/UnaryOperationAnimation'; +import { ArraySpreadAnimation } from './animationComponents/ArraySpreadAnimation'; import { isNode } from './components/ControlStack'; import { Frame } from './components/Frame'; import { ArrayValue } from './components/values/ArrayValue'; @@ -276,6 +277,40 @@ export class CseAnimation { ) ); break; + case InstrType.SPREAD: + const prevcontrol = Layout.previousControlComponent.stackItemComponents; + const control = Layout.controlComponent.stackItemComponents; + const array = Layout.previousStashComponent.stashItemComponents.at(-1)!.arrow!.target! as ArrayValue; + let currCallInstr; + let prevCallInstr; + + + + for (let i = 0; control.at(-i) != undefined; i++) { + if (control.at(-i)?.text.includes("call ")) { // find call instr above + currCallInstr = control.at(-i); + break; + } + } + + for (let i = 0; prevcontrol.at(-i) != undefined; i++) { + if (prevcontrol.at(-i)?.text.includes("call ")) { // find call instr above + prevCallInstr = prevcontrol.at(-i); + break; + } + } + + CseAnimation.animations.push( + new ArraySpreadAnimation( + lastControlComponent, + Layout.previousStashComponent.stashItemComponents.at(-1)!, + Layout.stashComponent.stashItemComponents.slice(-array.data.length)!, + currCallInstr!, + prevCallInstr! + ) + + ); + break; case InstrType.ARRAY_LENGTH: case InstrType.BREAK: case InstrType.BREAK_MARKER: diff --git a/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx b/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx new file mode 100644 index 0000000000..5b3f9c098f --- /dev/null +++ b/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx @@ -0,0 +1,185 @@ +import React from 'react'; +import { Group } from 'react-konva'; + +import { ControlItemComponent } from '../components/ControlItemComponent'; +import { StashItemComponent } from '../components/StashItemComponent'; +import { Visible } from '../components/Visible'; +import { ControlStashConfig } from '../CseMachineControlStashConfig'; +import { + defaultActiveColor, + defaultDangerColor, + defaultStrokeColor, + getTextWidth +} from '../CseMachineUtils'; +import { Animatable, AnimationConfig } from './base/Animatable'; +import { AnimatedGenericArrow } from './base/AnimatedGenericArrow'; +import { AnimatedTextbox } from './base/AnimatedTextbox'; +import { getNodePosition } from './base/AnimationUtils'; +import { Easings } from 'konva/lib/Tween'; + +/** + * Adapted from InstructionApplicationAnimation, but changed resultAnimation to [], among others + */ +export class ArraySpreadAnimation extends Animatable { + private controlInstrAnimation: AnimatedTextbox; // the array literal control item + private stashItemAnimation: AnimatedTextbox; + private resultAnimations: AnimatedTextbox[]; + private arrowAnimation?: AnimatedGenericArrow; + private currCallInstrAnimation: AnimatedTextbox; + private prevCallInstrAnimation: AnimatedTextbox; + + private endX: number; + private resultItemIsFirst: boolean; + + constructor( + private controlInstrItem: ControlItemComponent, + private stashItem: StashItemComponent, + private resultItems: StashItemComponent[], + private currCallInstrItem: ControlItemComponent, + private prevCallInstrItem: ControlItemComponent + ) { + super(); + this.resultItemIsFirst = (resultItems[0]?.index ?? stashItem.index) === 0; + this.endX = stashItem!.x() + stashItem!.width(); + this.controlInstrAnimation = new AnimatedTextbox( + controlInstrItem.text, + getNodePosition(controlInstrItem), + { rectProps: { stroke: defaultActiveColor() } } + ); + this.stashItemAnimation = new AnimatedTextbox(stashItem.text, getNodePosition(stashItem), { + rectProps: { + stroke: defaultDangerColor() + } + }); + + // call instr above + this.prevCallInstrAnimation = new AnimatedTextbox( + this.prevCallInstrItem.text, + { ...getNodePosition(this.prevCallInstrItem), + opacity: 0 }, + { rectProps: { stroke: defaultActiveColor() } } + ); + + this.currCallInstrAnimation = new AnimatedTextbox( + this.currCallInstrItem.text, + { ...getNodePosition(this.currCallInstrItem), + opacity: 0 }, + { rectProps: { stroke: defaultActiveColor() } } + ); + + this.resultAnimations = resultItems.map(item => { + return new AnimatedTextbox(item.text, { + ...getNodePosition(item), + opacity: 0 + })}); + if (stashItem.arrow) { + this.arrowAnimation = new AnimatedGenericArrow(stashItem.arrow, { opacity: 0 }); + } + } + + draw(): React.ReactNode { + return ( + + {this.controlInstrAnimation.draw()} + {this.stashItemAnimation.draw()} + {this.currCallInstrAnimation.draw()} + {this.prevCallInstrAnimation.draw()} + {this.resultAnimations.map(a => a.draw())} + {this.arrowAnimation?.draw()} + + ); + } + + async animate(animationConfig?: AnimationConfig) { + this.resultItems?.map(a => a.ref.current?.hide()); + this.resultItems?.map(a => a.arrow?.ref.current?.hide()); + const minInstrWidth = + getTextWidth(this.controlInstrItem.text) + ControlStashConfig.ControlItemTextPadding * 2; + const resultX = (idx: number) => this.resultItems[idx]?.x() ?? this.stashItem.x(); + const resultY = this.resultItems[0]?.y() ?? this.stashItem.y(); + const startX = resultX(0) - (this.resultItemIsFirst ? minInstrWidth : 0); + const fadeDuration = ((animationConfig?.duration ?? 1) * 3) / 4; + const fadeInDelay = (animationConfig?.delay ?? 0) + (animationConfig?.duration ?? 1) / 4; + + + // Move spread instruction next to stash item (array pointer) + await Promise.all([ + + // Show change in call arity + this.prevCallInstrAnimation.animateTo( + { scaleX: 1.1, scaleY: 1.1, opacity: 0 }, + { duration: 0.3, easing: Easings.StrongEaseOut } + ), + + this.currCallInstrAnimation.animateTo( + { opacity: 1 }, + { duration: 0.3, easing: Easings.StrongEaseOut } + ), + + + + ...this.resultAnimations.flatMap(a => [ + + a.animateTo( + { x: startX + (this.endX - startX) / 2 - this.resultItems[0]!.width() / 2 }, + { duration: 0 } + ) + ]), + this.controlInstrAnimation.animateRectTo({ stroke: defaultStrokeColor() }, animationConfig), + this.controlInstrAnimation.animateTo( + { + x: startX, + y: + resultY + + (this.resultItemIsFirst + ? 0 + : (this.resultItems[0].height() ?? this.stashItem.height())), + width: minInstrWidth + }, + animationConfig + ), + this.stashItemAnimation.animateRectTo({ stroke: defaultDangerColor() }, animationConfig) + + ]); + + + + + + + animationConfig = { ...animationConfig, delay: 0 }; + // Merge all elements together to form the result + await Promise.all([ + this.controlInstrAnimation.animateTo({ x: resultX(0), y: resultY }, animationConfig), + this.controlInstrAnimation.animateTo( + { opacity: 0 }, + { ...animationConfig, duration: fadeDuration } + ), + this.stashItemAnimation.animateTo({ x: resultX(0) }, animationConfig), + this.stashItemAnimation.animateTo({ opacity: 0 }, { ...animationConfig, duration: fadeDuration }), + + ...this.resultAnimations.flatMap((a, idx) => [ + a.animateTo({ x: resultX(idx) }, animationConfig), + a.animateRectTo({ stroke: defaultDangerColor() }, animationConfig), + a.animateTo( + { opacity: 1 }, + { ...animationConfig, duration: fadeDuration, delay: fadeInDelay } + ), + ]), + + ]); + + + this.destroy(); + } + + destroy() { + this.ref.current?.hide(); + this.resultItems.map(a => a.ref.current?.show()); + this.resultItems.map(a => a.arrow?.ref.current?.show()); + this.controlInstrAnimation.destroy(); + this.stashItemAnimation.destroy(); + this.resultAnimations.map(a => a.destroy()); + this.arrowAnimation?.destroy(); + } +} From 62a3fdc37b51cb975254e76d5437b7996e61a800 Mon Sep 17 00:00:00 2001 From: alsonleej Date: Thu, 13 Mar 2025 03:27:43 +0800 Subject: [PATCH 02/11] spread animation --- src/features/cseMachine/CseMachineAnimation.tsx | 2 +- .../cseMachine/animationComponents/ArraySpreadAnimation.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/cseMachine/CseMachineAnimation.tsx b/src/features/cseMachine/CseMachineAnimation.tsx index 9cf20e0134..c26639b355 100644 --- a/src/features/cseMachine/CseMachineAnimation.tsx +++ b/src/features/cseMachine/CseMachineAnimation.tsx @@ -6,6 +6,7 @@ import React from 'react'; import { ArrayAccessAnimation } from './animationComponents/ArrayAccessAnimation'; import { ArrayAssignmentAnimation } from './animationComponents/ArrayAssignmentAnimation'; +import { ArraySpreadAnimation } from './animationComponents/ArraySpreadAnimation'; import { AssignmentAnimation } from './animationComponents/AssignmentAnimation'; import { Animatable } from './animationComponents/base/Animatable'; import { lookupBinding } from './animationComponents/base/AnimationUtils'; @@ -20,7 +21,6 @@ import { InstructionApplicationAnimation } from './animationComponents/Instructi import { LookupAnimation } from './animationComponents/LookupAnimation'; import { PopAnimation } from './animationComponents/PopAnimation'; import { UnaryOperationAnimation } from './animationComponents/UnaryOperationAnimation'; -import { ArraySpreadAnimation } from './animationComponents/ArraySpreadAnimation'; import { isNode } from './components/ControlStack'; import { Frame } from './components/Frame'; import { ArrayValue } from './components/values/ArrayValue'; diff --git a/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx b/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx index 5b3f9c098f..555af0c307 100644 --- a/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx +++ b/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx @@ -1,3 +1,4 @@ +import { Easings } from 'konva/lib/Tween'; import React from 'react'; import { Group } from 'react-konva'; @@ -15,7 +16,6 @@ import { Animatable, AnimationConfig } from './base/Animatable'; import { AnimatedGenericArrow } from './base/AnimatedGenericArrow'; import { AnimatedTextbox } from './base/AnimatedTextbox'; import { getNodePosition } from './base/AnimationUtils'; -import { Easings } from 'konva/lib/Tween'; /** * Adapted from InstructionApplicationAnimation, but changed resultAnimation to [], among others From caf48a0f94af05dca7d9a7393f1c5c5dff354090 Mon Sep 17 00:00:00 2001 From: alsonleej Date: Thu, 13 Mar 2025 19:03:11 +0800 Subject: [PATCH 03/11] fixed bug with empty array, and animated control instr --- src/features/cseMachine/CseMachineAnimation.tsx | 12 +++++++++++- .../animationComponents/ArraySpreadAnimation.tsx | 14 +++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/features/cseMachine/CseMachineAnimation.tsx b/src/features/cseMachine/CseMachineAnimation.tsx index c26639b355..e40cf5cd4f 100644 --- a/src/features/cseMachine/CseMachineAnimation.tsx +++ b/src/features/cseMachine/CseMachineAnimation.tsx @@ -113,6 +113,11 @@ export class CseAnimation { ); } break; + case 'SpreadElement': + CseAnimation.animations.push( + new ControlExpansionAnimation(lastControlComponent, CseAnimation.getNewControlItems()), + ); + break; case 'AssignmentExpression': case 'ArrayExpression': case 'BinaryExpression': @@ -281,6 +286,9 @@ export class CseAnimation { const prevcontrol = Layout.previousControlComponent.stackItemComponents; const control = Layout.controlComponent.stackItemComponents; const array = Layout.previousStashComponent.stashItemComponents.at(-1)!.arrow!.target! as ArrayValue; + + console.log(array); + let currCallInstr; let prevCallInstr; @@ -299,12 +307,14 @@ export class CseAnimation { break; } } + + const resultItems = array.data.length !== 0 ? Layout.stashComponent.stashItemComponents.slice(-array.data.length) : []; CseAnimation.animations.push( new ArraySpreadAnimation( lastControlComponent, Layout.previousStashComponent.stashItemComponents.at(-1)!, - Layout.stashComponent.stashItemComponents.slice(-array.data.length)!, + resultItems!, currCallInstr!, prevCallInstr! ) diff --git a/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx b/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx index 555af0c307..ff51ef2307 100644 --- a/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx +++ b/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx @@ -29,7 +29,6 @@ export class ArraySpreadAnimation extends Animatable { private prevCallInstrAnimation: AnimatedTextbox; private endX: number; - private resultItemIsFirst: boolean; constructor( private controlInstrItem: ControlItemComponent, @@ -39,7 +38,10 @@ export class ArraySpreadAnimation extends Animatable { private prevCallInstrItem: ControlItemComponent ) { super(); - this.resultItemIsFirst = (resultItems[0]?.index ?? stashItem.index) === 0; + + console.log(stashItem); + console.log(resultItems); + this.endX = stashItem!.x() + stashItem!.width(); this.controlInstrAnimation = new AnimatedTextbox( controlInstrItem.text, @@ -97,7 +99,7 @@ export class ArraySpreadAnimation extends Animatable { getTextWidth(this.controlInstrItem.text) + ControlStashConfig.ControlItemTextPadding * 2; const resultX = (idx: number) => this.resultItems[idx]?.x() ?? this.stashItem.x(); const resultY = this.resultItems[0]?.y() ?? this.stashItem.y(); - const startX = resultX(0) - (this.resultItemIsFirst ? minInstrWidth : 0); + const startX = resultX(0); const fadeDuration = ((animationConfig?.duration ?? 1) * 3) / 4; const fadeInDelay = (animationConfig?.delay ?? 0) + (animationConfig?.duration ?? 1) / 4; @@ -121,7 +123,7 @@ export class ArraySpreadAnimation extends Animatable { ...this.resultAnimations.flatMap(a => [ a.animateTo( - { x: startX + (this.endX - startX) / 2 - this.resultItems[0]!.width() / 2 }, + { x: startX + (this.endX - startX) / 2 - this.resultItems[0]?.width() / 2 }, { duration: 0 } ) ]), @@ -131,9 +133,7 @@ export class ArraySpreadAnimation extends Animatable { x: startX, y: resultY + - (this.resultItemIsFirst - ? 0 - : (this.resultItems[0].height() ?? this.stashItem.height())), + (this.resultItems[0]?.height() ?? this.stashItem.height()), width: minInstrWidth }, animationConfig From 08ba90d15cee3da82f771f339a0146e02287ae56 Mon Sep 17 00:00:00 2001 From: alsonleej Date: Thu, 13 Mar 2025 19:04:29 +0800 Subject: [PATCH 04/11] fixed bug with empty array, and animated control instr --- .../cseMachine/CseMachineAnimation.tsx | 21 +++--- .../ArraySpreadAnimation.tsx | 65 ++++++++----------- 2 files changed, 38 insertions(+), 48 deletions(-) diff --git a/src/features/cseMachine/CseMachineAnimation.tsx b/src/features/cseMachine/CseMachineAnimation.tsx index e40cf5cd4f..f91a3e04d5 100644 --- a/src/features/cseMachine/CseMachineAnimation.tsx +++ b/src/features/cseMachine/CseMachineAnimation.tsx @@ -115,7 +115,7 @@ export class CseAnimation { break; case 'SpreadElement': CseAnimation.animations.push( - new ControlExpansionAnimation(lastControlComponent, CseAnimation.getNewControlItems()), + new ControlExpansionAnimation(lastControlComponent, CseAnimation.getNewControlItems()) ); break; case 'AssignmentExpression': @@ -285,31 +285,35 @@ export class CseAnimation { case InstrType.SPREAD: const prevcontrol = Layout.previousControlComponent.stackItemComponents; const control = Layout.controlComponent.stackItemComponents; - const array = Layout.previousStashComponent.stashItemComponents.at(-1)!.arrow!.target! as ArrayValue; + const array = Layout.previousStashComponent.stashItemComponents.at(-1)!.arrow! + .target! as ArrayValue; console.log(array); let currCallInstr; let prevCallInstr; - - for (let i = 0; control.at(-i) != undefined; i++) { - if (control.at(-i)?.text.includes("call ")) { // find call instr above + if (control.at(-i)?.text.includes('call ')) { + // find call instr above currCallInstr = control.at(-i); break; } } for (let i = 0; prevcontrol.at(-i) != undefined; i++) { - if (prevcontrol.at(-i)?.text.includes("call ")) { // find call instr above + if (prevcontrol.at(-i)?.text.includes('call ')) { + // find call instr above prevCallInstr = prevcontrol.at(-i); break; } } - const resultItems = array.data.length !== 0 ? Layout.stashComponent.stashItemComponents.slice(-array.data.length) : []; - + const resultItems = + array.data.length !== 0 + ? Layout.stashComponent.stashItemComponents.slice(-array.data.length) + : []; + CseAnimation.animations.push( new ArraySpreadAnimation( lastControlComponent, @@ -318,7 +322,6 @@ export class CseAnimation { currCallInstr!, prevCallInstr! ) - ); break; case InstrType.ARRAY_LENGTH: diff --git a/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx b/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx index ff51ef2307..dd0fb77f02 100644 --- a/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx +++ b/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx @@ -45,35 +45,34 @@ export class ArraySpreadAnimation extends Animatable { this.endX = stashItem!.x() + stashItem!.width(); this.controlInstrAnimation = new AnimatedTextbox( controlInstrItem.text, - getNodePosition(controlInstrItem), + getNodePosition(controlInstrItem), { rectProps: { stroke: defaultActiveColor() } } ); this.stashItemAnimation = new AnimatedTextbox(stashItem.text, getNodePosition(stashItem), { - rectProps: { - stroke: defaultDangerColor() - } - }); + rectProps: { + stroke: defaultDangerColor() + } + }); // call instr above this.prevCallInstrAnimation = new AnimatedTextbox( this.prevCallInstrItem.text, - { ...getNodePosition(this.prevCallInstrItem), - opacity: 0 }, + { ...getNodePosition(this.prevCallInstrItem), opacity: 0 }, { rectProps: { stroke: defaultActiveColor() } } ); this.currCallInstrAnimation = new AnimatedTextbox( this.currCallInstrItem.text, - { ...getNodePosition(this.currCallInstrItem), - opacity: 0 }, + { ...getNodePosition(this.currCallInstrItem), opacity: 0 }, { rectProps: { stroke: defaultActiveColor() } } ); this.resultAnimations = resultItems.map(item => { return new AnimatedTextbox(item.text, { - ...getNodePosition(item), - opacity: 0 - })}); + ...getNodePosition(item), + opacity: 0 + }); + }); if (stashItem.arrow) { this.arrowAnimation = new AnimatedGenericArrow(stashItem.arrow, { opacity: 0 }); } @@ -103,10 +102,8 @@ export class ArraySpreadAnimation extends Animatable { const fadeDuration = ((animationConfig?.duration ?? 1) * 3) / 4; const fadeInDelay = (animationConfig?.delay ?? 0) + (animationConfig?.duration ?? 1) / 4; - // Move spread instruction next to stash item (array pointer) await Promise.all([ - // Show change in call arity this.prevCallInstrAnimation.animateTo( { scaleX: 1.1, scaleY: 1.1, opacity: 0 }, @@ -118,35 +115,24 @@ export class ArraySpreadAnimation extends Animatable { { duration: 0.3, easing: Easings.StrongEaseOut } ), - - ...this.resultAnimations.flatMap(a => [ - - a.animateTo( - { x: startX + (this.endX - startX) / 2 - this.resultItems[0]?.width() / 2 }, - { duration: 0 } - ) - ]), + a.animateTo( + { x: startX + (this.endX - startX) / 2 - this.resultItems[0]?.width() / 2 }, + { duration: 0 } + ) + ]), this.controlInstrAnimation.animateRectTo({ stroke: defaultStrokeColor() }, animationConfig), this.controlInstrAnimation.animateTo( { x: startX, - y: - resultY + - (this.resultItems[0]?.height() ?? this.stashItem.height()), + y: resultY + (this.resultItems[0]?.height() ?? this.stashItem.height()), width: minInstrWidth }, animationConfig ), this.stashItemAnimation.animateRectTo({ stroke: defaultDangerColor() }, animationConfig) - ]); - - - - - animationConfig = { ...animationConfig, delay: 0 }; // Merge all elements together to form the result await Promise.all([ @@ -156,20 +142,21 @@ export class ArraySpreadAnimation extends Animatable { { ...animationConfig, duration: fadeDuration } ), this.stashItemAnimation.animateTo({ x: resultX(0) }, animationConfig), - this.stashItemAnimation.animateTo({ opacity: 0 }, { ...animationConfig, duration: fadeDuration }), + this.stashItemAnimation.animateTo( + { opacity: 0 }, + { ...animationConfig, duration: fadeDuration } + ), ...this.resultAnimations.flatMap((a, idx) => [ a.animateTo({ x: resultX(idx) }, animationConfig), a.animateRectTo({ stroke: defaultDangerColor() }, animationConfig), - a.animateTo( - { opacity: 1 }, - { ...animationConfig, duration: fadeDuration, delay: fadeInDelay } - ), - ]), - + a.animateTo( + { opacity: 1 }, + { ...animationConfig, duration: fadeDuration, delay: fadeInDelay } + ) + ]) ]); - this.destroy(); } From 19d9e35087dee70e02f4695edd73b49fb1d829d6 Mon Sep 17 00:00:00 2001 From: alsonleej Date: Mon, 17 Mar 2025 19:38:24 +0800 Subject: [PATCH 05/11] added animation for array access out of range --- .github/workflows/ci.yml | 2 +- CONTRIBUTING.md | 2 +- README.md | 2 +- _config.yml | 2 +- craco.config.js | 59 +++++++++--------- eslint.config.mjs | 4 +- public/externalLibs/sound/soundToneMatrix.js | 6 +- public/index.html | 62 ++++++++++--------- public/manifest.json | 8 +-- .../cseMachine/CseMachineAnimation.tsx | 2 - .../ArrayAccessAnimation.tsx | 29 ++++++++- .../ArraySpreadAnimation.tsx | 3 - src/i18n/locales/en/login.json | 4 +- src/i18n/locales/pseudo/login.json | 4 +- 14 files changed, 107 insertions(+), 82 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8f1f27cd8..f080a209ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ env: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - + jobs: lint: if: github.event.pull_request.draft == false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c32d7e20c..a251aba277 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ To start contributing, create a fork from our repo and send a PR. Refer to [this The frontend comes with an extensive test suite. To run the tests after you made your modifications, run `yarn test`. Regression tests are run automatically when you want to push changes to this repository. -The regression tests are generated using `jest` and stored as snapshots in `src/\_\_tests\_\_`. After modifying the frontend, carefully inspect any failing regression tests reported in red in the command line. If you are convinced that the regression tests and not your changes are at fault, you can update the regression tests by running: +The regression tests are generated using `jest` and stored as snapshots in `src/\_\_tests\_\_`. After modifying the frontend, carefully inspect any failing regression tests reported in red in the command line. If you are convinced that the regression tests and not your changes are at fault, you can update the regression tests by running: ```bash yarn test --updateSnapshot diff --git a/README.md b/README.md index 16ebdfb83d..5d0afb0ee7 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The Source Academy () is an immersive online experie 1. Clone this repository and navigate to it using your command line -1. Install the version of `yarn` as specified in `package.json`, `packageManager`. +1. Install the version of `yarn` as specified in `package.json`, `packageManager`. > We recommend using `corepack` to manage the version of `yarn`, you may simply run `corepack enable` to complete this step. diff --git a/_config.yml b/_config.yml index c4192631f2..277f1f2c51 100644 --- a/_config.yml +++ b/_config.yml @@ -1 +1 @@ -theme: jekyll-theme-cayman \ No newline at end of file +theme: jekyll-theme-cayman diff --git a/craco.config.js b/craco.config.js index fdc586735f..e73de64b6a 100644 --- a/craco.config.js +++ b/craco.config.js @@ -40,17 +40,17 @@ const cracoConfig = { // Polyfill Node.js core modules. // An empty implementation (false) is provided when there is no browser equivalent. webpackConfig.resolve.fallback = { - 'child_process': false, - 'constants': require.resolve('constants-browserify'), - 'fs': false, - 'http': require.resolve('stream-http'), - 'https': require.resolve('https-browserify'), - 'os': require.resolve('os-browserify/browser'), + child_process: false, + constants: require.resolve('constants-browserify'), + fs: false, + http: require.resolve('stream-http'), + https: require.resolve('https-browserify'), + os: require.resolve('os-browserify/browser'), 'path/posix': require.resolve('path-browserify'), 'process/browser': require.resolve('process/browser'), - 'stream': require.resolve('stream-browserify'), - 'timers': require.resolve('timers-browserify'), - 'url': require.resolve('url/'), + stream: require.resolve('stream-browserify'), + timers: require.resolve('timers-browserify'), + url: require.resolve('url/') }; // workaround .mjs files by Acorn @@ -60,31 +60,34 @@ const cracoConfig = { type: 'javascript/auto', resolve: { fullySpecified: false - }, + } }); - webpackConfig.ignoreWarnings = [{ - // Ignore warnings for dependencies that do not ship with a source map. - // This is because we cannot do anything about our dependencies. - module: /node_modules/, - message: /Failed to parse source map/ - }, { - // Ignore the warnings that occur because js-slang uses dynamic imports - // to load Source modules - module: /js-slang\/dist\/modules\/loader\/loaders.js/, - message: /Critical dependency: the request of a dependency is an expression/ - }]; + webpackConfig.ignoreWarnings = [ + { + // Ignore warnings for dependencies that do not ship with a source map. + // This is because we cannot do anything about our dependencies. + module: /node_modules/, + message: /Failed to parse source map/ + }, + { + // Ignore the warnings that occur because js-slang uses dynamic imports + // to load Source modules + module: /js-slang\/dist\/modules\/loader\/loaders.js/, + message: /Critical dependency: the request of a dependency is an expression/ + } + ]; webpackConfig.plugins = [ ...webpackConfig.plugins, // Make environment variables available in the browser by polyfilling the 'process' Node.js module. new webpack.ProvidePlugin({ - process: 'process/browser', + process: 'process/browser' }), // Make the 'buffer' Node.js module available in the browser. new webpack.ProvidePlugin({ - Buffer: ['buffer', 'Buffer'], - }), + Buffer: ['buffer', 'Buffer'] + }) ]; // Workaround to suppress warnings caused by ts-morph in js-slang @@ -156,16 +159,14 @@ const cracoConfig = { jestConfig.setupFiles = [ ...jestConfig.setupFiles, './src/i18n/i18n.ts' // Setup i18next configuration - ] + ]; return jestConfig; } }, babel: { - presets: [ - ['@babel/preset-typescript'] - ] + presets: [['@babel/preset-typescript']] } -} +}; const ignoreModulePaths = (...paths) => { const moduleRoot = replaceSlashes('/node_modules/'); diff --git a/eslint.config.mjs b/eslint.config.mjs index 89a6d244f6..d08bbe8adf 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,7 +4,7 @@ import { config, configs } from 'typescript-eslint'; import reactPlugin from 'eslint-plugin-react'; import reactHooksPlugin from 'eslint-plugin-react-hooks'; -import simpleImportSort from 'eslint-plugin-simple-import-sort' +import simpleImportSort from 'eslint-plugin-simple-import-sort'; // import reactRefresh from 'eslint-plugin-react-refresh'; export default config( @@ -24,7 +24,7 @@ export default config( files: ['**/*.ts*'], plugins: { 'react-hooks': reactHooksPlugin, - 'react': reactPlugin, + react: reactPlugin, 'simple-import-sort': simpleImportSort }, rules: { diff --git a/public/externalLibs/sound/soundToneMatrix.js b/public/externalLibs/sound/soundToneMatrix.js index 8638a90378..d0246757bf 100644 --- a/public/externalLibs/sound/soundToneMatrix.js +++ b/public/externalLibs/sound/soundToneMatrix.js @@ -36,7 +36,7 @@ var timeout_matrix; // for coloring the matrix accordingly while it's being played var timeout_color; -var timeout_objects = new Array(); +var timeout_objects = []; // vector_to_list returns a list that contains the elements of the argument vector // in the given order. @@ -54,7 +54,7 @@ function vector_to_list(vector) { function x_y_to_row_column(x, y) { var row = Math.floor((y - margin_length) / (square_side_length + distance_between_squares)); var column = Math.floor((x - margin_length) / (square_side_length + distance_between_squares)); - return Array(row, column); + return [row, column]; } // given the row number of a square, return the leftmost coordinate @@ -365,5 +365,5 @@ function clear_all_timeout() { clearTimeout(timeout_objects[i]); } - timeout_objects = new Array(); + timeout_objects = []; } diff --git a/public/index.html b/public/index.html index bf37250c90..90dabb55e9 100644 --- a/public/index.html +++ b/public/index.html @@ -1,23 +1,30 @@ - + - - - - - - - - - - - - - - - - Source Academy - - + Source Academy + + - - -
- - - + --> diff --git a/public/manifest.json b/public/manifest.json index b799aebed9..891708851b 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -13,16 +13,16 @@ "type": "image/png" }, { - "src": "icons/android-chrome-256x256.png", - "sizes": "256x256", - "type": "image/png" + "src": "icons/android-chrome-256x256.png", + "sizes": "256x256", + "type": "image/png" }, { "src": "icons/maskable.png", "sizes": "196x196", "type": "image/png", "purpose": "maskable" - } + } ], "start_url": "./", "display": "standalone", diff --git a/src/features/cseMachine/CseMachineAnimation.tsx b/src/features/cseMachine/CseMachineAnimation.tsx index f91a3e04d5..49d2489efb 100644 --- a/src/features/cseMachine/CseMachineAnimation.tsx +++ b/src/features/cseMachine/CseMachineAnimation.tsx @@ -288,8 +288,6 @@ export class CseAnimation { const array = Layout.previousStashComponent.stashItemComponents.at(-1)!.arrow! .target! as ArrayValue; - console.log(array); - let currCallInstr; let prevCallInstr; diff --git a/src/features/cseMachine/animationComponents/ArrayAccessAnimation.tsx b/src/features/cseMachine/animationComponents/ArrayAccessAnimation.tsx index d233117238..b39f121f1b 100644 --- a/src/features/cseMachine/animationComponents/ArrayAccessAnimation.tsx +++ b/src/features/cseMachine/animationComponents/ArrayAccessAnimation.tsx @@ -28,6 +28,7 @@ export class ArrayAccessAnimation extends Animatable { private resultAnimation: AnimatedTextbox; private resultArrowAnimation?: AnimatedGenericArrow; private arrayUnit: ArrayUnit; + private out: boolean; constructor( private accInstr: ControlItemComponent, @@ -46,12 +47,29 @@ export class ArrayAccessAnimation extends Animatable { rectProps: { stroke: defaultDangerColor() } }); this.arrayArrowAnimation = new AnimatedGenericArrow(arrayItem.arrow!); + // if index is out of range + this.out = false; // the target should always be an array value const array = arrayItem.arrow!.target! as ArrayValue; - this.arrayUnit = array.units[parseInt(indexItem.text)]; + + // if index access is out of range. if index access is negative, error should be thrown from js-slang at this point + const arraylen = array.data.length; + + if (parseInt(indexItem.text) >= arraylen) { + this.out = true; + this.arrayUnit = array.units[arraylen - 1]; + } else { + this.arrayUnit = array.units[parseInt(indexItem.text)]; + } + this.resultAnimation = new AnimatedTextbox(resultItem.text, { ...getNodeDimensions(resultItem), - x: this.arrayUnit.x() + this.arrayUnit.width() / 2 - this.resultItem.width() / 2, + // if array index out of range, animate to one unit beyond array + x: + this.arrayUnit.x() + + this.arrayUnit.width() / 2 - + this.resultItem.width() / 2 + + (this.out ? this.arrayUnit.width() : 0), y: this.arrayUnit.y() + this.arrayUnit.height() / 2 - this.resultItem.height() / 2, opacity: 0 }); @@ -79,7 +97,12 @@ export class ArrayAccessAnimation extends Animatable { const minInstrItemWidth = getTextWidth(this.accInstr.text) + ControlStashConfig.ControlItemTextPadding * 2; const indexAboveArrayLocation = { - x: this.arrayUnit.x() + this.arrayUnit.width() / 2 - this.indexItem.width() / 2, + // if array index out of range, animate to one unit beyond array + x: + this.arrayUnit.x() + + this.arrayUnit.width() / 2 - + this.indexItem.width() / 2 + + (this.out ? this.arrayUnit.width() : 0), y: this.arrayUnit.y() - this.indexItem.height() - 8 }; const indexInArrayLocation = { diff --git a/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx b/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx index dd0fb77f02..ba16389f0b 100644 --- a/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx +++ b/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx @@ -39,9 +39,6 @@ export class ArraySpreadAnimation extends Animatable { ) { super(); - console.log(stashItem); - console.log(resultItems); - this.endX = stashItem!.x() + stashItem!.width(); this.controlInstrAnimation = new AnimatedTextbox( controlInstrItem.text, diff --git a/src/i18n/locales/en/login.json b/src/i18n/locales/en/login.json index 01617e7fc6..ca4bf45059 100644 --- a/src/i18n/locales/en/login.json +++ b/src/i18n/locales/en/login.json @@ -1,4 +1,4 @@ { - "Logging In": "Logging In...", - "Log in with": "Log in with {{name}}" + "Logging In": "Logging In...", + "Log in with": "Log in with {{name}}" } diff --git a/src/i18n/locales/pseudo/login.json b/src/i18n/locales/pseudo/login.json index e95fc90dda..eeb499fe79 100644 --- a/src/i18n/locales/pseudo/login.json +++ b/src/i18n/locales/pseudo/login.json @@ -1,4 +1,4 @@ { - "Logging In": "Lògging Ìn...", - "Log in with": "Lòg in with {{name}}" + "Logging In": "Lògging Ìn...", + "Log in with": "Lòg in with {{name}}" } From 68843b6f74b1364fa8c03097f2bf36b2c2293cee Mon Sep 17 00:00:00 2001 From: alsonleej Date: Mon, 17 Mar 2025 20:32:44 +0800 Subject: [PATCH 06/11] add highlight to change in call arity --- .../cseMachine/CseMachineAnimation.tsx | 16 +++--------- .../ArraySpreadAnimation.tsx | 26 +++---------------- 2 files changed, 6 insertions(+), 36 deletions(-) diff --git a/src/features/cseMachine/CseMachineAnimation.tsx b/src/features/cseMachine/CseMachineAnimation.tsx index 49d2489efb..2a5b797c8b 100644 --- a/src/features/cseMachine/CseMachineAnimation.tsx +++ b/src/features/cseMachine/CseMachineAnimation.tsx @@ -283,15 +283,14 @@ export class CseAnimation { ); break; case InstrType.SPREAD: - const prevcontrol = Layout.previousControlComponent.stackItemComponents; const control = Layout.controlComponent.stackItemComponents; const array = Layout.previousStashComponent.stashItemComponents.at(-1)!.arrow! .target! as ArrayValue; let currCallInstr; - let prevCallInstr; - for (let i = 0; control.at(-i) != undefined; i++) { + for (let i = 1; control.at(-i) != undefined; i++) { + console.log(control.at(-i)); if (control.at(-i)?.text.includes('call ')) { // find call instr above currCallInstr = control.at(-i); @@ -299,14 +298,6 @@ export class CseAnimation { } } - for (let i = 0; prevcontrol.at(-i) != undefined; i++) { - if (prevcontrol.at(-i)?.text.includes('call ')) { - // find call instr above - prevCallInstr = prevcontrol.at(-i); - break; - } - } - const resultItems = array.data.length !== 0 ? Layout.stashComponent.stashItemComponents.slice(-array.data.length) @@ -317,8 +308,7 @@ export class CseAnimation { lastControlComponent, Layout.previousStashComponent.stashItemComponents.at(-1)!, resultItems!, - currCallInstr!, - prevCallInstr! + currCallInstr! ) ); break; diff --git a/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx b/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx index ba16389f0b..cca8b4a375 100644 --- a/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx +++ b/src/features/cseMachine/animationComponents/ArraySpreadAnimation.tsx @@ -1,4 +1,4 @@ -import { Easings } from 'konva/lib/Tween'; +//import { Easings } from 'konva/lib/Tween'; import React from 'react'; import { Group } from 'react-konva'; @@ -26,7 +26,6 @@ export class ArraySpreadAnimation extends Animatable { private resultAnimations: AnimatedTextbox[]; private arrowAnimation?: AnimatedGenericArrow; private currCallInstrAnimation: AnimatedTextbox; - private prevCallInstrAnimation: AnimatedTextbox; private endX: number; @@ -34,8 +33,7 @@ export class ArraySpreadAnimation extends Animatable { private controlInstrItem: ControlItemComponent, private stashItem: StashItemComponent, private resultItems: StashItemComponent[], - private currCallInstrItem: ControlItemComponent, - private prevCallInstrItem: ControlItemComponent + private currCallInstrItem: ControlItemComponent ) { super(); @@ -52,15 +50,9 @@ export class ArraySpreadAnimation extends Animatable { }); // call instr above - this.prevCallInstrAnimation = new AnimatedTextbox( - this.prevCallInstrItem.text, - { ...getNodePosition(this.prevCallInstrItem), opacity: 0 }, - { rectProps: { stroke: defaultActiveColor() } } - ); - this.currCallInstrAnimation = new AnimatedTextbox( this.currCallInstrItem.text, - { ...getNodePosition(this.currCallInstrItem), opacity: 0 }, + getNodePosition(this.currCallInstrItem), { rectProps: { stroke: defaultActiveColor() } } ); @@ -81,7 +73,6 @@ export class ArraySpreadAnimation extends Animatable { {this.controlInstrAnimation.draw()} {this.stashItemAnimation.draw()} {this.currCallInstrAnimation.draw()} - {this.prevCallInstrAnimation.draw()} {this.resultAnimations.map(a => a.draw())} {this.arrowAnimation?.draw()} @@ -101,17 +92,6 @@ export class ArraySpreadAnimation extends Animatable { // Move spread instruction next to stash item (array pointer) await Promise.all([ - // Show change in call arity - this.prevCallInstrAnimation.animateTo( - { scaleX: 1.1, scaleY: 1.1, opacity: 0 }, - { duration: 0.3, easing: Easings.StrongEaseOut } - ), - - this.currCallInstrAnimation.animateTo( - { opacity: 1 }, - { duration: 0.3, easing: Easings.StrongEaseOut } - ), - ...this.resultAnimations.flatMap(a => [ a.animateTo( { x: startX + (this.endX - startX) / 2 - this.resultItems[0]?.width() / 2 }, From bdc422186494aba88a5aef3539db0382957dafe1 Mon Sep 17 00:00:00 2001 From: alsonleej Date: Mon, 17 Mar 2025 21:49:57 +0800 Subject: [PATCH 07/11] remove console log --- src/features/cseMachine/CseMachineAnimation.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/features/cseMachine/CseMachineAnimation.tsx b/src/features/cseMachine/CseMachineAnimation.tsx index 2a5b797c8b..9f549351fa 100644 --- a/src/features/cseMachine/CseMachineAnimation.tsx +++ b/src/features/cseMachine/CseMachineAnimation.tsx @@ -290,7 +290,6 @@ export class CseAnimation { let currCallInstr; for (let i = 1; control.at(-i) != undefined; i++) { - console.log(control.at(-i)); if (control.at(-i)?.text.includes('call ')) { // find call instr above currCallInstr = control.at(-i); From 1992dd7fa7a96b8a1b0e37b1aed3d2442e1c3748 Mon Sep 17 00:00:00 2001 From: ALSON LEE JIAN YANG <97039161+alsonleej@users.noreply.github.com> Date: Fri, 4 Apr 2025 20:20:56 +0800 Subject: [PATCH 08/11] Update index.html --- public/index.html | 62 +++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/public/index.html b/public/index.html index 90dabb55e9..bf37250c90 100644 --- a/public/index.html +++ b/public/index.html @@ -1,30 +1,23 @@ - + - - - - - - - - - - - - - - - Source Academy - - + Source Academy + + - - -
- + --> + + From 7413d87fc5a94f5b1c40265279160f616c650d25 Mon Sep 17 00:00:00 2001 From: ALSON LEE JIAN YANG <97039161+alsonleej@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:25:12 +0800 Subject: [PATCH 09/11] Update craco.config.js --- craco.config.js | 238 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 209 insertions(+), 29 deletions(-) diff --git a/craco.config.js b/craco.config.js index e73de64b6a..fa69113eb7 100644 --- a/craco.config.js +++ b/craco.config.js @@ -40,17 +40,17 @@ const cracoConfig = { // Polyfill Node.js core modules. // An empty implementation (false) is provided when there is no browser equivalent. webpackConfig.resolve.fallback = { - child_process: false, - constants: require.resolve('constants-browserify'), - fs: false, - http: require.resolve('stream-http'), - https: require.resolve('https-browserify'), - os: require.resolve('os-browserify/browser'), + 'child_process': false, + 'constants': require.resolve('constants-browserify'), + 'fs': false, + 'http': require.resolve('stream-http'), + 'https': require.resolve('https-browserify'), + 'os': require.resolve('os-browserify/browser'), 'path/posix': require.resolve('path-browserify'), 'process/browser': require.resolve('process/browser'), - stream: require.resolve('stream-browserify'), - timers: require.resolve('timers-browserify'), - url: require.resolve('url/') + 'stream': require.resolve('stream-browserify'), + 'timers': require.resolve('timers-browserify'), + 'url': require.resolve('url/'), }; // workaround .mjs files by Acorn @@ -60,34 +60,31 @@ const cracoConfig = { type: 'javascript/auto', resolve: { fullySpecified: false - } + }, }); - webpackConfig.ignoreWarnings = [ - { - // Ignore warnings for dependencies that do not ship with a source map. - // This is because we cannot do anything about our dependencies. - module: /node_modules/, - message: /Failed to parse source map/ - }, - { - // Ignore the warnings that occur because js-slang uses dynamic imports - // to load Source modules - module: /js-slang\/dist\/modules\/loader\/loaders.js/, - message: /Critical dependency: the request of a dependency is an expression/ - } - ]; + webpackConfig.ignoreWarnings = [{ + // Ignore warnings for dependencies that do not ship with a source map. + // This is because we cannot do anything about our dependencies. + module: /node_modules/, + message: /Failed to parse source map/ + }, { + // Ignore the warnings that occur because js-slang uses dynamic imports + // to load Source modules + module: /js-slang\/dist\/modules\/loader\/loaders.js/, + message: /Critical dependency: the request of a dependency is an expression/ + }]; webpackConfig.plugins = [ ...webpackConfig.plugins, // Make environment variables available in the browser by polyfilling the 'process' Node.js module. new webpack.ProvidePlugin({ - process: 'process/browser' + process: 'process/browser', }), // Make the 'buffer' Node.js module available in the browser. new webpack.ProvidePlugin({ - Buffer: ['buffer', 'Buffer'] - }) + Buffer: ['buffer', 'Buffer'], + }), ]; // Workaround to suppress warnings caused by ts-morph in js-slang @@ -159,15 +156,198 @@ const cracoConfig = { jestConfig.setupFiles = [ ...jestConfig.setupFiles, './src/i18n/i18n.ts' // Setup i18next configuration - ]; + ] return jestConfig; } }, babel: { - presets: [['@babel/preset-typescript']] + presets: [ + ['@babel/preset-typescript'] + ] } +} + +const ignoreModulePaths = (...paths) => { + const moduleRoot = replaceSlashes('/node_modules/'); + const modulePaths = paths + .map(replaceSlashes) + .map(path => `(${path}[/\\\\.*])`) + .join('|'); + return moduleRoot + '(?!' + modulePaths + ').*.(js|jsx|ts|tsx)$'; +}; +const replaceSlashes = target => { + return target.replaceAll('/', '[/\\\\]'); }; +module.exports = cracoConfig;/* eslint-disable @typescript-eslint/no-require-imports */ +const webpack = require('webpack'); + +const cracoConfig = { + webpack: { + configure: webpackConfig => { + // avoid the entire process.env being inserted into the service worker + // if SW_EXCLUDE_REGEXES is unset + const definePlugin = webpackConfig.plugins.find( + plugin => plugin.constructor.name === 'DefinePlugin' + ); + const inlineProcessEnv = definePlugin.definitions['process.env']; + if (!inlineProcessEnv.REACT_APP_SW_EXCLUDE_REGEXES) { + inlineProcessEnv.REACT_APP_SW_EXCLUDE_REGEXES = undefined; + } + + const injectManifestPlugin = webpackConfig.plugins.find( + plugin => plugin.constructor.name === 'InjectManifest' + ); + if (injectManifestPlugin) { + injectManifestPlugin.config.maximumFileSizeToCacheInBytes = 20 * 1024 * 1024; + } + + // add rules to pack WASM (for Sourceror) + const wasmExtensionRegExp = /\.wasm$/; + webpackConfig.resolve.extensions.push('.wasm'); + webpackConfig.module.rules.forEach(rule => { + (rule.oneOf || []).forEach(oneOf => { + if (oneOf.type === 'asset/resource') { + oneOf.exclude.push(wasmExtensionRegExp); + } + }); + }); + // See https://webpack.js.org/configuration/experiments/#experiments. + webpackConfig.experiments = { + syncWebAssembly: true + }; + webpackConfig.output.webassemblyModuleFilename = 'static/[hash].module.wasm'; + + // Polyfill Node.js core modules. + // An empty implementation (false) is provided when there is no browser equivalent. + webpackConfig.resolve.fallback = { + 'child_process': false, + 'constants': require.resolve('constants-browserify'), + 'fs': false, + 'http': require.resolve('stream-http'), + 'https': require.resolve('https-browserify'), + 'os': require.resolve('os-browserify/browser'), + 'path/posix': require.resolve('path-browserify'), + 'process/browser': require.resolve('process/browser'), + 'stream': require.resolve('stream-browserify'), + 'timers': require.resolve('timers-browserify'), + 'url': require.resolve('url/'), + }; + + // workaround .mjs files by Acorn + webpackConfig.module.rules.push({ + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + resolve: { + fullySpecified: false + }, + }); + + webpackConfig.ignoreWarnings = [{ + // Ignore warnings for dependencies that do not ship with a source map. + // This is because we cannot do anything about our dependencies. + module: /node_modules/, + message: /Failed to parse source map/ + }, { + // Ignore the warnings that occur because js-slang uses dynamic imports + // to load Source modules + module: /js-slang\/dist\/modules\/loader\/loaders.js/, + message: /Critical dependency: the request of a dependency is an expression/ + }]; + + webpackConfig.plugins = [ + ...webpackConfig.plugins, + // Make environment variables available in the browser by polyfilling the 'process' Node.js module. + new webpack.ProvidePlugin({ + process: 'process/browser', + }), + // Make the 'buffer' Node.js module available in the browser. + new webpack.ProvidePlugin({ + Buffer: ['buffer', 'Buffer'], + }), + ]; + + // Workaround to suppress warnings caused by ts-morph in js-slang + webpackConfig.module.noParse = /node_modules\/@ts-morph\/common\/dist\/typescript\.js$/; + + return webpackConfig; + } + }, + jest: { + configure: jestConfig => { + jestConfig.transformIgnorePatterns = [ + // Will give something like + // '[/\\\\]node_modules[/\\\\] + // (?! + // ( @ion-phaser[/\\\\]react[/\\\\.*] )| + // ( js-slang[/\\\\.*] )| + // ( array-move[/\\\\.*] )| + // ... + // ( comma-separated-tokens[/\\\\.*] ) + // ).*.(js|jsx|ts|tsx)$' + ignoreModulePaths( + '@ion-phaser/react', + 'js-slang', + 'array-move', + 'konva', + 'react-konva', + 'react-debounce-render', + 'devlop', + 'hastscript', + 'hast-to-hyperscript', + 'hast-util-.+', + 'mdast-util-.+', + 'micromark', + 'micromark-.+', + 'vfile', + 'vfile-message', + 'unist-util-.+', + 'web-namespaces', + 'rehype-react', + 'unified', + 'bail', + 'is-plain-obj', + 'trough', + 'decode-named-character-reference', + 'character-entities', + 'trim-lines', + 'property-information', + 'space-separated-tokens', + 'comma-separated-tokens', + 'query-string', + 'decode-uri-component', + 'split-on-first', + 'filter-obj', + '@sourceacademy/c-slang', + 'java-parser', + 'conductor' + ), + '^.+\\.module\\.(css|sass|scss)$' + ]; + jestConfig.moduleNameMapper['unist-util-visit-parents/do-not-use-color'] = + '/node_modules/unist-util-visit-parents/lib'; + jestConfig.moduleNameMapper['vfile/do-not-use-conditional-minpath'] = + '/node_modules/vfile/lib'; + jestConfig.moduleNameMapper['vfile/do-not-use-conditional-minproc'] = + '/node_modules/vfile/lib'; + jestConfig.moduleNameMapper['vfile/do-not-use-conditional-minurl'] = + '/node_modules/vfile/lib'; + + jestConfig.setupFiles = [ + ...jestConfig.setupFiles, + './src/i18n/i18n.ts' // Setup i18next configuration + ] + return jestConfig; + } + }, + babel: { + presets: [ + ['@babel/preset-typescript'] + ] + } +} + const ignoreModulePaths = (...paths) => { const moduleRoot = replaceSlashes('/node_modules/'); const modulePaths = paths From 017bceac504cbebc20893e86322a73d02ce20baa Mon Sep 17 00:00:00 2001 From: ALSON LEE JIAN YANG <97039161+alsonleej@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:30:11 +0800 Subject: [PATCH 10/11] Update ArrayAccessAnimation.tsx --- .../animationComponents/ArrayAccessAnimation.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/features/cseMachine/animationComponents/ArrayAccessAnimation.tsx b/src/features/cseMachine/animationComponents/ArrayAccessAnimation.tsx index b39f121f1b..7219cafe92 100644 --- a/src/features/cseMachine/animationComponents/ArrayAccessAnimation.tsx +++ b/src/features/cseMachine/animationComponents/ArrayAccessAnimation.tsx @@ -28,7 +28,7 @@ export class ArrayAccessAnimation extends Animatable { private resultAnimation: AnimatedTextbox; private resultArrowAnimation?: AnimatedGenericArrow; private arrayUnit: ArrayUnit; - private out: boolean; + private outOfRange: boolean; constructor( private accInstr: ControlItemComponent, @@ -48,7 +48,7 @@ export class ArrayAccessAnimation extends Animatable { }); this.arrayArrowAnimation = new AnimatedGenericArrow(arrayItem.arrow!); // if index is out of range - this.out = false; + this.outOfRange = false; // the target should always be an array value const array = arrayItem.arrow!.target! as ArrayValue; @@ -56,7 +56,7 @@ export class ArrayAccessAnimation extends Animatable { const arraylen = array.data.length; if (parseInt(indexItem.text) >= arraylen) { - this.out = true; + this.outOfRange = true; this.arrayUnit = array.units[arraylen - 1]; } else { this.arrayUnit = array.units[parseInt(indexItem.text)]; @@ -69,7 +69,7 @@ export class ArrayAccessAnimation extends Animatable { this.arrayUnit.x() + this.arrayUnit.width() / 2 - this.resultItem.width() / 2 + - (this.out ? this.arrayUnit.width() : 0), + (this.outOfRange ? this.arrayUnit.width() : 0), y: this.arrayUnit.y() + this.arrayUnit.height() / 2 - this.resultItem.height() / 2, opacity: 0 }); @@ -102,7 +102,7 @@ export class ArrayAccessAnimation extends Animatable { this.arrayUnit.x() + this.arrayUnit.width() / 2 - this.indexItem.width() / 2 + - (this.out ? this.arrayUnit.width() : 0), + (this.outOfRange ? this.arrayUnit.width() : 0), y: this.arrayUnit.y() - this.indexItem.height() - 8 }; const indexInArrayLocation = { From 1f4c517030e8c20367de84840960fe5166df901a Mon Sep 17 00:00:00 2001 From: ALSON LEE JIAN YANG <97039161+alsonleej@users.noreply.github.com> Date: Sun, 6 Apr 2025 16:34:04 +0800 Subject: [PATCH 11/11] Update craco.config.js --- craco.config.js | 181 ------------------------------------------------ 1 file changed, 181 deletions(-) diff --git a/craco.config.js b/craco.config.js index fa69113eb7..fdc586735f 100644 --- a/craco.config.js +++ b/craco.config.js @@ -179,185 +179,4 @@ const replaceSlashes = target => { return target.replaceAll('/', '[/\\\\]'); }; -module.exports = cracoConfig;/* eslint-disable @typescript-eslint/no-require-imports */ -const webpack = require('webpack'); - -const cracoConfig = { - webpack: { - configure: webpackConfig => { - // avoid the entire process.env being inserted into the service worker - // if SW_EXCLUDE_REGEXES is unset - const definePlugin = webpackConfig.plugins.find( - plugin => plugin.constructor.name === 'DefinePlugin' - ); - const inlineProcessEnv = definePlugin.definitions['process.env']; - if (!inlineProcessEnv.REACT_APP_SW_EXCLUDE_REGEXES) { - inlineProcessEnv.REACT_APP_SW_EXCLUDE_REGEXES = undefined; - } - - const injectManifestPlugin = webpackConfig.plugins.find( - plugin => plugin.constructor.name === 'InjectManifest' - ); - if (injectManifestPlugin) { - injectManifestPlugin.config.maximumFileSizeToCacheInBytes = 20 * 1024 * 1024; - } - - // add rules to pack WASM (for Sourceror) - const wasmExtensionRegExp = /\.wasm$/; - webpackConfig.resolve.extensions.push('.wasm'); - webpackConfig.module.rules.forEach(rule => { - (rule.oneOf || []).forEach(oneOf => { - if (oneOf.type === 'asset/resource') { - oneOf.exclude.push(wasmExtensionRegExp); - } - }); - }); - // See https://webpack.js.org/configuration/experiments/#experiments. - webpackConfig.experiments = { - syncWebAssembly: true - }; - webpackConfig.output.webassemblyModuleFilename = 'static/[hash].module.wasm'; - - // Polyfill Node.js core modules. - // An empty implementation (false) is provided when there is no browser equivalent. - webpackConfig.resolve.fallback = { - 'child_process': false, - 'constants': require.resolve('constants-browserify'), - 'fs': false, - 'http': require.resolve('stream-http'), - 'https': require.resolve('https-browserify'), - 'os': require.resolve('os-browserify/browser'), - 'path/posix': require.resolve('path-browserify'), - 'process/browser': require.resolve('process/browser'), - 'stream': require.resolve('stream-browserify'), - 'timers': require.resolve('timers-browserify'), - 'url': require.resolve('url/'), - }; - - // workaround .mjs files by Acorn - webpackConfig.module.rules.push({ - test: /\.mjs$/, - include: /node_modules/, - type: 'javascript/auto', - resolve: { - fullySpecified: false - }, - }); - - webpackConfig.ignoreWarnings = [{ - // Ignore warnings for dependencies that do not ship with a source map. - // This is because we cannot do anything about our dependencies. - module: /node_modules/, - message: /Failed to parse source map/ - }, { - // Ignore the warnings that occur because js-slang uses dynamic imports - // to load Source modules - module: /js-slang\/dist\/modules\/loader\/loaders.js/, - message: /Critical dependency: the request of a dependency is an expression/ - }]; - - webpackConfig.plugins = [ - ...webpackConfig.plugins, - // Make environment variables available in the browser by polyfilling the 'process' Node.js module. - new webpack.ProvidePlugin({ - process: 'process/browser', - }), - // Make the 'buffer' Node.js module available in the browser. - new webpack.ProvidePlugin({ - Buffer: ['buffer', 'Buffer'], - }), - ]; - - // Workaround to suppress warnings caused by ts-morph in js-slang - webpackConfig.module.noParse = /node_modules\/@ts-morph\/common\/dist\/typescript\.js$/; - - return webpackConfig; - } - }, - jest: { - configure: jestConfig => { - jestConfig.transformIgnorePatterns = [ - // Will give something like - // '[/\\\\]node_modules[/\\\\] - // (?! - // ( @ion-phaser[/\\\\]react[/\\\\.*] )| - // ( js-slang[/\\\\.*] )| - // ( array-move[/\\\\.*] )| - // ... - // ( comma-separated-tokens[/\\\\.*] ) - // ).*.(js|jsx|ts|tsx)$' - ignoreModulePaths( - '@ion-phaser/react', - 'js-slang', - 'array-move', - 'konva', - 'react-konva', - 'react-debounce-render', - 'devlop', - 'hastscript', - 'hast-to-hyperscript', - 'hast-util-.+', - 'mdast-util-.+', - 'micromark', - 'micromark-.+', - 'vfile', - 'vfile-message', - 'unist-util-.+', - 'web-namespaces', - 'rehype-react', - 'unified', - 'bail', - 'is-plain-obj', - 'trough', - 'decode-named-character-reference', - 'character-entities', - 'trim-lines', - 'property-information', - 'space-separated-tokens', - 'comma-separated-tokens', - 'query-string', - 'decode-uri-component', - 'split-on-first', - 'filter-obj', - '@sourceacademy/c-slang', - 'java-parser', - 'conductor' - ), - '^.+\\.module\\.(css|sass|scss)$' - ]; - jestConfig.moduleNameMapper['unist-util-visit-parents/do-not-use-color'] = - '/node_modules/unist-util-visit-parents/lib'; - jestConfig.moduleNameMapper['vfile/do-not-use-conditional-minpath'] = - '/node_modules/vfile/lib'; - jestConfig.moduleNameMapper['vfile/do-not-use-conditional-minproc'] = - '/node_modules/vfile/lib'; - jestConfig.moduleNameMapper['vfile/do-not-use-conditional-minurl'] = - '/node_modules/vfile/lib'; - - jestConfig.setupFiles = [ - ...jestConfig.setupFiles, - './src/i18n/i18n.ts' // Setup i18next configuration - ] - return jestConfig; - } - }, - babel: { - presets: [ - ['@babel/preset-typescript'] - ] - } -} - -const ignoreModulePaths = (...paths) => { - const moduleRoot = replaceSlashes('/node_modules/'); - const modulePaths = paths - .map(replaceSlashes) - .map(path => `(${path}[/\\\\.*])`) - .join('|'); - return moduleRoot + '(?!' + modulePaths + ').*.(js|jsx|ts|tsx)$'; -}; -const replaceSlashes = target => { - return target.replaceAll('/', '[/\\\\]'); -}; - module.exports = cracoConfig;