├── .node-version
├── .husky
└── pre-push
├── .yarnrc.yml
├── _config.yml
├── src
├── react-app-env.d.ts
├── fileMock.ts
├── assets
│ ├── SA.jpg
│ ├── default-avatar.jpg
│ ├── login_background.jpg
│ ├── academy_background.jpg
│ ├── default_cover_image.jpg
│ └── leaderboard_background.jpg
├── commons
│ ├── application
│ │ ├── types
│ │ │ ├── VscodeTypes.ts
│ │ │ ├── CommonsTypes.ts
│ │ │ └── ExternalTypes.ts
│ │ ├── actions
│ │ │ ├── VscodeActions.ts
│ │ │ ├── __tests__
│ │ │ │ └── CommonsActions.test.ts
│ │ │ └── CommonsActions.ts
│ │ ├── reducers
│ │ │ ├── CommonsReducer.ts
│ │ │ └── VscodeReducer.ts
│ │ └── __tests__
│ │ │ ├── __snapshots__
│ │ │ └── Application.test.tsx.snap
│ │ │ └── Application.test.tsx
│ ├── repl
│ │ └── ReplTypes.ts
│ ├── fileSystem
│ │ ├── FileSystemTypes.ts
│ │ ├── FileSystemActions.ts
│ │ ├── __tests__
│ │ │ ├── FileSystemActions.test.ts
│ │ │ └── FileSystemReducer.test.ts
│ │ └── FileSystemReducer.ts
│ ├── gitHubOverlay
│ │ └── GitHubFileNodeData.tsx
│ ├── featureFlags
│ │ ├── featureSelector.ts
│ │ ├── useFeature.ts
│ │ ├── selectFeatureSaga.ts
│ │ ├── publicFlags.ts
│ │ └── FeatureFlag.ts
│ ├── sideContent
│ │ ├── __tests__
│ │ │ └── __snapshots__
│ │ │ │ └── SideContentHtmlDisplay.test.tsx.snap
│ │ └── content
│ │ │ ├── SideContentCanvasOutput.tsx
│ │ │ └── remoteExecution
│ │ │ └── DeviceMenuItemButtons.tsx
│ ├── utils
│ │ ├── LocalStorageHelper.ts
│ │ ├── __tests__
│ │ │ └── GitHubPersistenceHelper.test.ts
│ │ ├── MemoizeHelper.ts
│ │ ├── SourcerorHelper.ts
│ │ ├── ConsoleOverload.ts
│ │ ├── GitHubPersistenceHelper.ts
│ │ ├── QueryHelper.ts
│ │ ├── CastBackend.ts
│ │ ├── ParamParseHelper.ts
│ │ └── notifications
│ │ │ └── NotificationsHelper.ts
│ ├── controlBar
│ │ ├── ControlBarResetButton.tsx
│ │ ├── ControlBarClearButton.tsx
│ │ ├── ControlBarCloseButton.tsx
│ │ ├── ControlBarSubmit.tsx
│ │ ├── ControlBarReturnToAcademyButton.tsx
│ │ ├── ControlBarQuestionViewButton.tsx
│ │ ├── ControlBarPreviousButton.tsx
│ │ ├── ControlBarToggleEditModeButton.tsx
│ │ ├── ControlBarExecutionTime.tsx
│ │ ├── ControlBarEvalButton.tsx
│ │ ├── ControlBarSaveButton.tsx
│ │ ├── ControlBar.tsx
│ │ └── ControlBarRunButton.tsx
│ ├── editor
│ │ ├── EditorHotkeys.ts
│ │ ├── __tests__
│ │ │ ├── __snapshots__
│ │ │ │ └── Editor.test.tsx.snap
│ │ │ ├── tabs
│ │ │ │ └── utils.ts
│ │ │ └── Editor.test.tsx
│ │ └── tabs
│ │ │ └── EditorTab.tsx
│ ├── __tests__
│ │ ├── ContentDisplay.test.tsx
│ │ └── __snapshots__
│ │ │ └── ContentDisplay.test.tsx.snap
│ ├── fileSystemView
│ │ └── FileSystemViewIndentationPadding.tsx
│ ├── navigationBar
│ │ └── subcomponents
│ │ │ └── __tests__
│ │ │ └── SicpNavigationBar.test.tsx
│ ├── assessment
│ │ └── AssessmentNotFound.tsx
│ ├── sagas
│ │ ├── FeatureFlagSaga.ts
│ │ ├── __tests__
│ │ │ └── GitHubPersistenceSaga.test.ts
│ │ └── WorkspaceSaga
│ │ │ └── helpers
│ │ │ ├── dumpDisplayBuffer.ts
│ │ │ └── restoreExtraMethods.ts
│ ├── delay
│ │ └── Delay.tsx
│ ├── achievement
│ │ ├── view
│ │ │ └── AchievementViewCompletion.tsx
│ │ ├── card
│ │ │ ├── AchievementXp.tsx
│ │ │ └── AchievementDeadline.tsx
│ │ ├── control
│ │ │ ├── achievementEditor
│ │ │ │ ├── AchievementTemplate.ts
│ │ │ │ ├── AchievementUuidCopier.tsx
│ │ │ │ └── AchievementAdder.tsx
│ │ │ ├── goalEditor
│ │ │ │ ├── GoalAdder.tsx
│ │ │ │ ├── EditableGoalTypes.ts
│ │ │ │ └── metaDetails
│ │ │ │ │ └── EditableManualMeta.tsx
│ │ │ └── common
│ │ │ │ ├── ItemDeleter.tsx
│ │ │ │ └── ItemSaver.tsx
│ │ ├── AchievementFilter.tsx
│ │ └── overview
│ │ │ └── AchievementMilestone.tsx
│ ├── notificationBadge
│ │ └── NotificationBadgeTypes.ts
│ ├── WorkspaceSettingsContext.tsx
│ ├── mobileWorkspace
│ │ └── mobileSideContent
│ │ │ └── MobileControlBar.tsx
│ ├── grading
│ │ ├── GradingText.tsx
│ │ └── GradingFlex.tsx
│ ├── editingWorkspaceSideContent
│ │ ├── EditingWorkspaceSideContentHelper.ts
│ │ └── EditingWorkspaceSideContentGradingTab.tsx
│ ├── ContentDisplay.tsx
│ ├── collabEditing
│ │ └── __tests__
│ │ │ └── CollabEditingActions.test.ts
│ └── Markdown.tsx
├── i18n
│ ├── locales
│ │ ├── zh-SG
│ │ │ ├── sideContent
│ │ │ │ ├── sessionManagement.json
│ │ │ │ ├── faceapiDisplay.json
│ │ │ │ ├── htmlDisplay.json
│ │ │ │ ├── contestVoting.json
│ │ │ │ ├── resultCard.json
│ │ │ │ ├── dataVisualizer.json
│ │ │ │ ├── upload.json
│ │ │ │ ├── substVisualizer.json
│ │ │ │ ├── contestLeaderboard.json
│ │ │ │ ├── autograder.json
│ │ │ │ └── cseMachine.json
│ │ │ ├── login.json
│ │ │ ├── stories.json
│ │ │ ├── sourcecast.json
│ │ │ ├── sourceRecorder.json
│ │ │ ├── welcome.json
│ │ │ ├── grading.json
│ │ │ └── commons.json
│ │ ├── en
│ │ │ ├── sideContent
│ │ │ │ ├── sessionManagement.json
│ │ │ │ ├── faceapiDisplay.json
│ │ │ │ ├── htmlDisplay.json
│ │ │ │ ├── contestVoting.json
│ │ │ │ ├── resultCard.json
│ │ │ │ ├── dataVisualizer.json
│ │ │ │ ├── upload.json
│ │ │ │ ├── contestLeaderboard.json
│ │ │ │ ├── autograder.json
│ │ │ │ └── substVisualizer.json
│ │ │ ├── login.json
│ │ │ ├── sourcecast.json
│ │ │ ├── stories.json
│ │ │ ├── sourceRecorder.json
│ │ │ ├── grading.json
│ │ │ ├── welcome.json
│ │ │ └── commons.json
│ │ ├── pseudo
│ │ │ ├── login.json
│ │ │ ├── index.ts
│ │ │ └── commons.json
│ │ └── index.ts
│ ├── i18next.d.ts
│ └── i18n.ts
├── styles
│ ├── Stories.module.scss
│ ├── _variableHighlighting.scss
│ ├── _game.scss
│ ├── _playground.scss
│ ├── NavigationBar.module.scss
│ ├── ConfirmDialog.module.scss
│ ├── Draggable.module.scss
│ ├── ContextMenu.module.scss
│ ├── Academy.module.scss
│ ├── ConfigureControls.module.scss
│ ├── _contributors.scss
│ ├── _sourcereel.scss
│ ├── _gamesimulator.scss
│ ├── Login.module.scss
│ ├── GradingCommentSelector.module.scss
│ ├── AchievementCommentCard.module.scss
│ ├── _application.scss
│ ├── _global.scss
│ └── _github.scss
├── features
│ ├── github
│ │ ├── GitHubTypes.ts
│ │ └── GitHubActions.ts
│ ├── dataVisualizer
│ │ ├── tree
│ │ │ ├── BaseTreeNode.ts
│ │ │ ├── TreeNode.ts
│ │ │ ├── DataTreeNode.tsx
│ │ │ ├── AlreadyParsedTreeNode.ts
│ │ │ ├── DrawableTreeNode.tsx
│ │ │ └── FunctionTreeNode.tsx
│ │ ├── dataVisualizerTypes.ts
│ │ ├── drawable
│ │ │ ├── Drawable.ts
│ │ │ ├── NullDrawable.tsx
│ │ │ └── ArrowDrawable.tsx
│ │ └── Config.ts
│ ├── sicp
│ │ ├── chatCompletion
│ │ │ └── sicpNotes.ts
│ │ ├── TableOfContentsButton.tsx
│ │ ├── utils
│ │ │ └── SicpUtils.ts
│ │ ├── TableOfContentsHelper.ts
│ │ ├── errors
│ │ │ └── SicpErrorBoundary.tsx
│ │ └── __tests__
│ │ │ └── TableOfContentsHelper.test.ts
│ ├── persistence
│ │ ├── PersistenceTypes.ts
│ │ └── PersistenceActions.ts
│ ├── game
│ │ ├── sound
│ │ │ └── GameSoundTypes.ts
│ │ ├── awards
│ │ │ └── GameAwardsTypes.ts
│ │ ├── dashboard
│ │ │ ├── GameDashboardTypes.ts
│ │ │ └── GameDashboardConstants.ts
│ │ ├── character
│ │ │ ├── GameCharacterConstants.ts
│ │ │ └── GameCharacterTypes.ts
│ │ ├── mode
│ │ │ ├── GameModeTypes.ts
│ │ │ ├── explore
│ │ │ │ └── GameModeExploreConstants.ts
│ │ │ ├── sequence
│ │ │ │ └── GameModeSequence.ts
│ │ │ ├── talk
│ │ │ │ └── GameModeTalkConstants.ts
│ │ │ ├── move
│ │ │ │ └── GameModeMoveConstants.ts
│ │ │ └── menu
│ │ │ │ └── GameModeMenuConstants.ts
│ │ ├── state
│ │ │ ├── GameStateConstants.ts
│ │ │ └── GameStateTypes.ts
│ │ ├── task
│ │ │ └── GameTaskTypes.ts
│ │ ├── quiz
│ │ │ └── GameQuizType.ts
│ │ ├── location
│ │ │ └── GameMapHelper.ts
│ │ ├── popUp
│ │ │ └── GamePopUpConstants.ts
│ │ ├── input
│ │ │ └── GameInputConstants.ts
│ │ ├── boundingBoxes
│ │ │ └── GameBoundingBoxTypes.ts
│ │ ├── chapter
│ │ │ └── GameChapterMocks.ts
│ │ ├── phase
│ │ │ └── GamePhaseTypes.ts
│ │ ├── layer
│ │ │ └── GameLayerTypes.ts
│ │ ├── utils
│ │ │ └── TextUtils.ts
│ │ ├── commons
│ │ │ └── CommonConstants.ts
│ │ ├── assets
│ │ │ ├── TextAssets.ts
│ │ │ ├── FontAssets.ts
│ │ │ └── AssetsTypes.ts
│ │ ├── scenes
│ │ │ ├── gameManager
│ │ │ │ └── GameManagerHelper.ts
│ │ │ ├── checkpointTransition
│ │ │ │ └── CheckpointTransitionConstants.ts
│ │ │ ├── bindings
│ │ │ │ └── BindingsConstants.ts
│ │ │ ├── settings
│ │ │ │ └── SettingsConstants.ts
│ │ │ └── mainMenu
│ │ │ │ └── MainMenuConstants.ts
│ │ ├── log
│ │ │ └── GameLogConstants.ts
│ │ └── dialogue
│ │ │ └── GameDialogueConstants.ts
│ ├── eventLogging
│ │ └── client.ts
│ ├── directory
│ │ ├── PluginDirectoryTypes.ts
│ │ ├── LanguageDirectoryTypes.ts
│ │ ├── PluginDirectoryActions.ts
│ │ ├── flagDirectoryLanguageEnable.ts
│ │ ├── flagDirectoryPluginUrl.ts
│ │ ├── flagDirectoryLanguageUrl.ts
│ │ ├── LanguageDirectoryActions.ts
│ │ ├── PluginDirectoryReducer.ts
│ │ └── LanguageDirectoryReducer.ts
│ ├── academy
│ │ ├── AcademyTypes.ts
│ │ └── AcademyActions.ts
│ ├── conductor
│ │ ├── flagConductorEnable.ts
│ │ ├── flagConductorEvaluatorUrl.ts
│ │ ├── createConductor.ts
│ │ └── BrowserHostPlugin.ts
│ ├── playground
│ │ ├── PlaygroundTypes.ts
│ │ └── __tests__
│ │ │ ├── PlaygroundReducer.test.ts
│ │ │ └── PlaygroundActions.test.ts
│ ├── sourceRecorder
│ │ ├── sourcereel
│ │ │ └── SourcereelTypes.ts
│ │ └── sourcecast
│ │ │ ├── SourcecastActions.ts
│ │ │ └── SourcecastTypes.ts
│ ├── teamFormation
│ │ └── TeamFormationTypes.ts
│ ├── dashboard
│ │ ├── DashboardTypes.ts
│ │ ├── DashboardActions.ts
│ │ └── DashboardReducer.ts
│ ├── gameSimulator
│ │ ├── GameSimulatorUtils.ts
│ │ └── GameSimulatorTypes.ts
│ ├── achievement
│ │ ├── __tests__
│ │ │ ├── AchievementReducer.test.ts
│ │ │ └── AchievementActions.test.ts
│ │ └── AchievementReducer.ts
│ ├── remoteExecution
│ │ ├── PeripheralContainer.tsx
│ │ └── RemoteExecutionActions.ts
│ ├── cseMachine
│ │ ├── components
│ │ │ ├── arrows
│ │ │ │ ├── ArrowFromFrame.tsx
│ │ │ │ └── ArrowFromStashItemComponent.tsx
│ │ │ └── Visible.tsx
│ │ └── CseMachineControlStashConfig.ts
│ ├── leaderboard
│ │ └── LeaderboardTypes.ts
│ └── groundControl
│ │ └── GroundControlActions.ts
├── pages
│ ├── welcome
│ │ └── Welcome.module.scss
│ ├── sicp
│ │ ├── subcomponents
│ │ │ ├── SicpLatex.tsx
│ │ │ ├── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── SicpLatex.test.tsx.snap
│ │ │ │ ├── SicpIndexPage.test.tsx
│ │ │ │ ├── SicpToc.test.tsx
│ │ │ │ ├── SicpLatex.test.tsx
│ │ │ │ └── CodeSnippet.test.tsx
│ │ │ └── chatbot
│ │ │ │ └── types.tsx
│ │ └── __tests__
│ │ │ └── __snapshots__
│ │ │ └── Sicp.test.tsx.snap
│ ├── academy
│ │ ├── grading
│ │ │ └── subcomponents
│ │ │ │ ├── GradingFilterable.tsx
│ │ │ │ ├── GradingColumnFilters.tsx
│ │ │ │ └── GradingSubmissionFilters.tsx
│ │ ├── teamFormation
│ │ │ └── subcomponents
│ │ │ │ ├── TeamFormationFilters.tsx
│ │ │ │ └── TeamFormationBadges.tsx
│ │ ├── groundControl
│ │ │ └── configureControls
│ │ │ │ ├── DispatchContestXpButton.tsx
│ │ │ │ └── CalculateContestScoreButton.tsx
│ │ ├── adminPanel
│ │ │ └── subcomponents
│ │ │ │ └── assessmentConfigPanel
│ │ │ │ ├── BooleanCell.tsx
│ │ │ │ └── NumericCell.tsx
│ │ └── gameSimulator
│ │ │ └── subcomponents
│ │ │ └── assetViewer
│ │ │ └── AssetViewerPreview.tsx
│ ├── notFound
│ │ └── NotFound.tsx
│ ├── contributors
│ │ └── Contributors.tsx
│ ├── login
│ │ └── Login.tsx
│ └── leaderboard
│ │ └── subcomponents
│ │ └── ContestLeaderboardWrapper.tsx
├── global.d.ts
└── bootstrap
│ ├── sentry.ts
│ └── agGrid.ts
├── public
├── assets
│ ├── pixel.png
│ ├── zekton.png
│ ├── zekton_dark.png
│ ├── alien_and_cows.png
│ ├── alien_league.png
│ ├── Sample_Profile_1.jpg
│ ├── Sample_Profile_2.jpg
│ ├── Sample_Profile_3.jpg
│ ├── Sample_Profile_4.jpg
│ ├── Sample_Profile_5.jpg
│ ├── Sample_Profile_6.jpg
│ ├── Sample_Profile_7.jpg
│ ├── mockRoomPreviewMapping.txt
│ ├── mockChapter2.txt
│ ├── mockChapter1.txt
│ ├── mockAwardsMapping.txt
│ └── mockDefaultCheckpoint.txt
├── icons
│ ├── favicon.ico
│ ├── maskable.png
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ └── android-chrome-256x256.png
└── manifest.json
├── .prettierrc
├── scripts
└── test-coveralls.sh
├── .github
├── dependabot.yml
└── pull_request_template.md
├── .vscode
└── settings.json
├── .editorconfig
├── renovate.json
├── tsconfig.json
├── .gitignore
└── vitest.config.ts
/.node-version:
--------------------------------------------------------------------------------
1 | 22.17.0
2 |
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | bash scripts/test.sh
2 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
2 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/fileMock.ts:
--------------------------------------------------------------------------------
1 | // For mocking binary files like images
2 | export default {};
3 |
--------------------------------------------------------------------------------
/src/assets/SA.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/src/assets/SA.jpg
--------------------------------------------------------------------------------
/public/assets/pixel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/assets/pixel.png
--------------------------------------------------------------------------------
/public/assets/zekton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/assets/zekton.png
--------------------------------------------------------------------------------
/public/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/icons/favicon.ico
--------------------------------------------------------------------------------
/src/commons/application/types/VscodeTypes.ts:
--------------------------------------------------------------------------------
1 | export type VscodeState = {
2 | isVscode: boolean;
3 | };
4 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/sideContent/sessionManagement.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "姓名",
3 | "role": "角色"
4 | }
5 |
--------------------------------------------------------------------------------
/public/icons/maskable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/icons/maskable.png
--------------------------------------------------------------------------------
/src/i18n/locales/en/sideContent/sessionManagement.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Name",
3 | "role": "Role"
4 | }
5 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/login.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging In": "登录中...",
3 | "Log in with": "使用 {{name}} 登录"
4 | }
5 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/sideContent/faceapiDisplay.json:
--------------------------------------------------------------------------------
1 | {
2 | "takePicture": "拍照",
3 | "reset": "重置"
4 | }
5 |
--------------------------------------------------------------------------------
/public/assets/zekton_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/assets/zekton_dark.png
--------------------------------------------------------------------------------
/src/assets/default-avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/src/assets/default-avatar.jpg
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/sideContent/htmlDisplay.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "HTML 显示",
3 | "label": "HTML 显示"
4 | }
5 |
--------------------------------------------------------------------------------
/src/styles/Stories.module.scss:
--------------------------------------------------------------------------------
1 | @import '_global';
2 |
3 | .highlight-row {
4 | background-color: #e0f2fe;
5 | }
6 |
--------------------------------------------------------------------------------
/public/assets/alien_and_cows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/assets/alien_and_cows.png
--------------------------------------------------------------------------------
/public/assets/alien_league.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/assets/alien_league.png
--------------------------------------------------------------------------------
/src/assets/login_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/src/assets/login_background.jpg
--------------------------------------------------------------------------------
/src/i18n/locales/en/login.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging In": "Logging In...",
3 | "Log in with": "Log in with {{name}}"
4 | }
5 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/sideContent/faceapiDisplay.json:
--------------------------------------------------------------------------------
1 | {
2 | "takePicture": "Take picture",
3 | "reset": "Reset"
4 | }
5 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/sideContent/htmlDisplay.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "HTML Display",
3 | "label": "HTML Display"
4 | }
5 |
--------------------------------------------------------------------------------
/public/assets/Sample_Profile_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/assets/Sample_Profile_1.jpg
--------------------------------------------------------------------------------
/public/assets/Sample_Profile_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/assets/Sample_Profile_2.jpg
--------------------------------------------------------------------------------
/public/assets/Sample_Profile_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/assets/Sample_Profile_3.jpg
--------------------------------------------------------------------------------
/public/assets/Sample_Profile_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/assets/Sample_Profile_4.jpg
--------------------------------------------------------------------------------
/public/assets/Sample_Profile_5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/assets/Sample_Profile_5.jpg
--------------------------------------------------------------------------------
/public/assets/Sample_Profile_6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/assets/Sample_Profile_6.jpg
--------------------------------------------------------------------------------
/public/assets/Sample_Profile_7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/assets/Sample_Profile_7.jpg
--------------------------------------------------------------------------------
/public/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/assets/academy_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/src/assets/academy_background.jpg
--------------------------------------------------------------------------------
/src/assets/default_cover_image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/src/assets/default_cover_image.jpg
--------------------------------------------------------------------------------
/src/i18n/locales/pseudo/login.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging In": "Lògging Ìn...",
3 | "Log in with": "Lòg in with {{name}}"
4 | }
5 |
--------------------------------------------------------------------------------
/src/assets/leaderboard_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/src/assets/leaderboard_background.jpg
--------------------------------------------------------------------------------
/public/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/icons/android-chrome-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/source-academy/frontend/HEAD/public/icons/android-chrome-256x256.png
--------------------------------------------------------------------------------
/src/features/github/GitHubTypes.ts:
--------------------------------------------------------------------------------
1 | export type GitHubSaveInfo = {
2 | repoName: string;
3 | filePath: string;
4 | lastSaved?: Date;
5 | };
6 |
--------------------------------------------------------------------------------
/public/assets/mockRoomPreviewMapping.txt:
--------------------------------------------------------------------------------
1 | 405
2 | /locations/yourRoom-dim/normal.png
3 |
4 | 404
5 | /locations/deathCube_ext/shields-down.png
6 |
--------------------------------------------------------------------------------
/src/i18n/locales/pseudo/index.ts:
--------------------------------------------------------------------------------
1 | import commons from './commons.json';
2 | import login from './login.json';
3 |
4 | export default {
5 | commons,
6 | login
7 | };
8 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/sideContent/contestVoting.json:
--------------------------------------------------------------------------------
1 | {
2 | "noEntries": "未找到符合条件的投票条目。",
3 | "title": "比赛投票",
4 | "tooltip": "从 D(最差)到 S(最佳)为您最喜欢的比赛条目排名!"
5 | }
6 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/stories.json:
--------------------------------------------------------------------------------
1 | {
2 | "stories": {
3 | "deleteConfirmation": "您确定要删除此故事吗?",
4 | "delete": "删除",
5 | "allStories": "所有故事"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "printWidth": 100,
5 | "arrowParens": "avoid",
6 | "trailingComma": "none",
7 | "endOfLine": "auto"
8 | }
9 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/sourcecast.json:
--------------------------------------------------------------------------------
1 | {
2 | "sourcecast": {
3 | "titleDescription": "标题:{{title}}\n描述:{{description}}",
4 | "introduction": "欢迎来到 Sourcecast!"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/features/dataVisualizer/tree/BaseTreeNode.ts:
--------------------------------------------------------------------------------
1 | export class TreeNode {
2 | public children: TreeNode[] | null;
3 |
4 | constructor() {
5 | this.children = null;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/features/sicp/chatCompletion/sicpNotes.ts:
--------------------------------------------------------------------------------
1 | import { SicpSection } from './chatCompletion';
2 |
3 | const sicpNotes: Partial> = {};
4 |
5 | export default sicpNotes;
6 |
--------------------------------------------------------------------------------
/src/styles/_variableHighlighting.scss:
--------------------------------------------------------------------------------
1 | .ace_variable_highlighting {
2 | z-index: 4;
3 | position: absolute;
4 | box-sizing: border-box;
5 | border: 1px dashed rgba(255, 255, 255, 0.6);
6 | }
7 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/sourcecast.json:
--------------------------------------------------------------------------------
1 | {
2 | "sourcecast": {
3 | "titleDescription": "Title: {{title}}\nDescription: {{description}}",
4 | "introduction": "Welcome to Sourcecast!"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/stories.json:
--------------------------------------------------------------------------------
1 | {
2 | "stories": {
3 | "deleteConfirmation": "Are you sure you want to delete this story?",
4 | "delete": "Delete",
5 | "allStories": "All Stories"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/commons/application/types/CommonsTypes.ts:
--------------------------------------------------------------------------------
1 | import type { createBrowserRouter } from 'react-router';
2 |
3 | export type Router = ReturnType;
4 | export type RouterState = Router | null;
5 |
--------------------------------------------------------------------------------
/src/features/persistence/PersistenceTypes.ts:
--------------------------------------------------------------------------------
1 | export type PersistenceState = 'INACTIVE' | 'SAVED' | 'DIRTY';
2 |
3 | export type PersistenceFile = {
4 | id: string;
5 | name: string;
6 | lastSaved?: Date;
7 | };
8 |
--------------------------------------------------------------------------------
/scripts/test-coveralls.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | ./scripts/coverage-fix.sh do && \
6 | yarn test --coverage --coverage.reporter=lcov \
7 | --coverage.exclude='**/src/features/game/**'
8 |
--------------------------------------------------------------------------------
/src/commons/repl/ReplTypes.ts:
--------------------------------------------------------------------------------
1 | import { InterpreterOutput } from '../application/ApplicationTypes';
2 |
3 | export type OutputProps = {
4 | output: InterpreterOutput;
5 | usingSubst?: boolean;
6 | isHtml?: boolean;
7 | };
8 |
--------------------------------------------------------------------------------
/src/styles/_game.scss:
--------------------------------------------------------------------------------
1 | #game-display {
2 | display: flex;
3 | flex-direction: column;
4 | width: 100%;
5 | align-items: center;
6 | }
7 |
8 | .fullscreen-button {
9 | position: absolute;
10 | z-index: 10;
11 | }
12 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/sourceRecorder.json:
--------------------------------------------------------------------------------
1 | {
2 | "sourceRecorder": {
3 | "deleteTitle": "删除 Sourcecast",
4 | "deleteConfirmation": "您确定要删除此 Sourcecast 条目吗?",
5 | "confirmDelete": "确认删除",
6 | "cancel": "取消"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/features/game/sound/GameSoundTypes.ts:
--------------------------------------------------------------------------------
1 | export const bgMusicFadeDuration = 1000;
2 |
3 | export const musicFadeOutTween = {
4 | volume: 0,
5 | ease: 'Power2'
6 | };
7 |
8 | export enum GameSoundType {
9 | SFX,
10 | BGM
11 | }
12 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: '/'
5 | schedule:
6 | interval: monthly
7 | time: '07:00'
8 | open-pull-requests-limit: 0 # soft disable for `renovate` migration test
9 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/sideContent/contestVoting.json:
--------------------------------------------------------------------------------
1 | {
2 | "noEntries": "There are no eligible entries for voting found.",
3 | "title": "Contest Voting",
4 | "tooltip": "Rank your favourite contest entries from tiers D (worst) to S (best)!"
5 | }
6 |
--------------------------------------------------------------------------------
/src/commons/fileSystem/FileSystemTypes.ts:
--------------------------------------------------------------------------------
1 | import { FSModule } from 'browserfs/dist/node/core/FS';
2 |
3 | export const SET_IN_BROWSER_FILE_SYSTEM = 'SET_IN_BROWSER_FILE_SYSTEM';
4 |
5 | export type FileSystemState = {
6 | inBrowserFileSystem: FSModule | null;
7 | };
8 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/sourceRecorder.json:
--------------------------------------------------------------------------------
1 | {
2 | "sourceRecorder": {
3 | "deleteTitle": "Delete Sourcecast",
4 | "deleteConfirmation": "Are you sure to delete this sourcecast entry?",
5 | "confirmDelete": "Confirm Delete",
6 | "cancel": "Cancel"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/features/github/GitHubActions.ts:
--------------------------------------------------------------------------------
1 | import { createActions } from 'src/commons/redux/utils';
2 |
3 | const newActions = createActions('github', {
4 | githubOpenFile: 0,
5 | githubSaveFile: 0,
6 | githubSaveFileAs: 0
7 | });
8 |
9 | export default newActions;
10 |
--------------------------------------------------------------------------------
/src/features/eventLogging/client.ts:
--------------------------------------------------------------------------------
1 | export const SYNC_LOGS = 'SYNC_LOGS';
2 |
3 | export function triggerSyncLogs(accessToken?: string) {
4 | if (!accessToken) {
5 | return;
6 | }
7 | navigator.serviceWorker.controller?.postMessage({ type: SYNC_LOGS, accessToken });
8 | }
9 |
--------------------------------------------------------------------------------
/src/features/directory/PluginDirectoryTypes.ts:
--------------------------------------------------------------------------------
1 | import { IPluginDefinition } from '@sourceacademy/plugin-directory/dist/types';
2 |
3 | export type PluginDirectoryState = {
4 | readonly plugins: IPluginDefinition[];
5 | readonly pluginMap: Record;
6 | };
7 |
--------------------------------------------------------------------------------
/src/pages/welcome/Welcome.module.scss:
--------------------------------------------------------------------------------
1 | .fullpage {
2 | margin-top: 20px;
3 | display: flex;
4 | justify-content: center;
5 | }
6 |
7 | .fullpage-content {
8 | display: flex;
9 | justify-content: center;
10 | }
11 |
12 | .text-left {
13 | text-align: left;
14 | }
15 |
--------------------------------------------------------------------------------
/src/styles/_playground.scss:
--------------------------------------------------------------------------------
1 | .Playground {
2 | height: 100%;
3 | display: flex;
4 | flex-direction: column;
5 | flex: 1 1 100%;
6 |
7 | .workspace {
8 | .ControlBar {
9 | .ControlBar_editingWorkspace {
10 | width: 0;
11 | }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.defaultFormatter": "esbenp.prettier-vscode",
3 | "eslint.useFlatConfig": true,
4 | "files.insertFinalNewline": true,
5 | "liveServer.settings.file": "/index.html",
6 | "liveServer.settings.NoBrowser": true,
7 | "liveServer.settings.root": "/build"
8 | }
9 |
--------------------------------------------------------------------------------
/src/features/dataVisualizer/tree/TreeNode.ts:
--------------------------------------------------------------------------------
1 | export { TreeNode } from './BaseTreeNode';
2 | export { DataTreeNode } from './DataTreeNode';
3 | export { DrawableTreeNode } from './DrawableTreeNode';
4 | export { FunctionTreeNode } from './FunctionTreeNode';
5 | export { ArrayTreeNode } from './ArrayTreeNode';
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | insert_final_newline = true
5 | end_of_line = lf
6 |
7 | [*.{ts,tsx,json}]
8 | indent_style = space
9 | indent_size = 2
10 |
11 | [*.md]
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.{html,css}]
16 | indent_style = space
17 | indent_size = 4
18 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/sideContent/resultCard.json:
--------------------------------------------------------------------------------
1 | {
2 | "timeout": "[超时] 提交的测试用例超出时间限制。",
3 | "syntax": "[语法] 第 {{line}} 行:{{errorExplanation}}",
4 | "runtime": "[运行时] 第 {{line}} 行:{{errorExplanation}}",
5 | "systemError": "[系统] {{errorMessage}}",
6 | "unknown": "[未知] 自动评分器错误:类型 {{errorType}}"
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/sicp/subcomponents/SicpLatex.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Latex from 'react-latex-next';
3 |
4 | type Props = {
5 | math: string;
6 | };
7 |
8 | const SicpLatex: React.FC = ({ math }) => {
9 | return {math};
10 | };
11 |
12 | export default SicpLatex;
13 |
--------------------------------------------------------------------------------
/public/assets/mockChapter2.txt:
--------------------------------------------------------------------------------
1 | startingLoc: emergency
2 |
3 | objectives
4 | finish
5 |
6 | emergency
7 | actions
8 | show_dialogue(chapter2)
9 | complete_objective(finish)
10 |
11 | dialogues
12 | chapter2
13 | Hold it, we have an emergency. You're now at chapter 2!
14 |
--------------------------------------------------------------------------------
/src/commons/application/actions/VscodeActions.ts:
--------------------------------------------------------------------------------
1 | import { createActions } from 'src/commons/redux/utils';
2 |
3 | const VscodeActions = createActions('vscode', {
4 | setVscode: 0
5 | });
6 |
7 | // For compatibility with existing code (actions helper)
8 | export default {
9 | ...VscodeActions
10 | };
11 |
--------------------------------------------------------------------------------
/src/features/game/awards/GameAwardsTypes.ts:
--------------------------------------------------------------------------------
1 | import { AssetKey, AssetPath, ItemId } from '../commons/CommonTypes';
2 |
3 | export type AwardProperty = {
4 | id: ItemId;
5 | assetKey: AssetKey;
6 | assetPath: AssetPath;
7 | title: string;
8 | description: string;
9 | completed: boolean;
10 | };
11 |
--------------------------------------------------------------------------------
/public/assets/mockChapter1.txt:
--------------------------------------------------------------------------------
1 | startingLoc: hallway
2 |
3 | objectives
4 | finish
5 |
6 | hallway
7 | actions
8 | show_dialogue(chapter1)
9 | complete_objective(finish)
10 |
11 | dialogues
12 | chapter1, What?
13 | You're now in chapter 1.
14 | I'm glad you're part of it
15 |
--------------------------------------------------------------------------------
/src/features/game/dashboard/GameDashboardTypes.ts:
--------------------------------------------------------------------------------
1 | export enum DashboardPage {
2 | Log = 'Log',
3 | Tasks = 'Tasks',
4 | Collectibles = 'Collectibles',
5 | Achievements = 'Achievements'
6 | }
7 |
8 | export interface DashboardPageManager {
9 | createUIContainer: () => Phaser.GameObjects.Container;
10 | }
11 |
--------------------------------------------------------------------------------
/src/styles/NavigationBar.module.scss:
--------------------------------------------------------------------------------
1 | @import '_global';
2 |
3 | .primary-navbar {
4 | background: #141e30; /* fallback for old browsers */
5 | background: -webkit-linear-gradient(to right, #141e30, #243b55); /* Chrome 10-25, Safari 5.1-6 */
6 | background: linear-gradient(to right, $cadet-color-1, $cadet-color-2);
7 | }
8 |
--------------------------------------------------------------------------------
/src/commons/gitHubOverlay/GitHubFileNodeData.tsx:
--------------------------------------------------------------------------------
1 | export class GitHubFileNodeData {
2 | childrenRetrieved: boolean = false;
3 | filePath: string = 'dummy_path';
4 | fileType: string = 'unset';
5 |
6 | constructor(path: string, type: string) {
7 | this.filePath = path;
8 | this.fileType = type;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/features/dataVisualizer/dataVisualizerTypes.ts:
--------------------------------------------------------------------------------
1 | // Source-related types
2 | export type Data = any;
3 | export type Pair = [Data, Data];
4 | export type EmptyList = null;
5 | export type List = [Data, List] | EmptyList;
6 |
7 | // Drawing-related types
8 | export type Drawing = JSX.Element;
9 | export type Step = Drawing[];
10 |
--------------------------------------------------------------------------------
/src/commons/application/actions/__tests__/CommonsActions.test.ts:
--------------------------------------------------------------------------------
1 | import CommonsActions from '../CommonsActions';
2 |
3 | test('logOut generates correct action object', () => {
4 | const action = CommonsActions.logOut();
5 | expect(action).toEqual({
6 | type: CommonsActions.logOut.type,
7 | payload: {}
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/sideContent/dataVisualizer.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultText": "数据可视化工具帮助您可视化数据结构。",
3 | "instructions": "通过调用函数 <0/> 激活,其中 <1/> 是您想要可视化的 <2/> 数据结构,<3/> 是结构的数量。",
4 | "reference": "数据可视化工具使用框图和指针图,这些图基于<0>《计算机程序的构造和解释,JavaScript 版》第 2 章第 2 节0>中介绍的符号。",
5 | "label": "数据可视化工具",
6 | "previous": "上一个",
7 | "next": "下一个"
8 | }
9 |
--------------------------------------------------------------------------------
/src/styles/ConfirmDialog.module.scss:
--------------------------------------------------------------------------------
1 | .ConfirmDialog {
2 | .large-button:not(:first-of-type) {
3 | margin-top: 0.5em;
4 | }
5 |
6 | @media only screen and (max-width: 500px) {
7 | // Set width to 98% when screen width is less than 500px
8 | // 500px is the default width of the component
9 | width: 98%;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/commons/featureFlags/featureSelector.ts:
--------------------------------------------------------------------------------
1 | import { OverallState } from '../application/ApplicationTypes';
2 | import { FeatureFlag } from './FeatureFlag';
3 |
4 | export function featureSelector(featureFlag: FeatureFlag) {
5 | return (state: OverallState) =>
6 | (state.featureFlags.modifiedFlags[featureFlag.flagName] || featureFlag.defaultValue) as T;
7 | }
8 |
--------------------------------------------------------------------------------
/src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpLatex.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Sicp latex renders > correctly block 1`] = `
4 |
5 | 1+1
6 |
7 | `;
8 |
9 | exports[`Sicp latex renders > correctly inline 1`] = `
10 |
11 | 1+1
12 |
13 | `;
14 |
--------------------------------------------------------------------------------
/src/i18n/i18next.d.ts:
--------------------------------------------------------------------------------
1 | import 'i18next';
2 |
3 | import { defaultLanguage, i18nLanguageCode } from './locales';
4 |
5 | export type i18nDefaultLangKeys = (typeof defaultLanguage)[i18nLanguageCode.DEFAULT];
6 |
7 | declare module 'i18next' {
8 | // Extend CustomTypeOptions
9 | interface CustomTypeOptions {
10 | resources: i18nDefaultLangKeys;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/pages/sicp/subcomponents/chatbot/types.tsx:
--------------------------------------------------------------------------------
1 | type ChatMessage = {
2 | role: 'user' | 'assistant';
3 | content: string;
4 | };
5 |
6 | type InitChatResponse = {
7 | response: ChatMessage;
8 | conversationId: string;
9 | maxContentSize: number;
10 | };
11 |
12 | type ContinueChatResponse = {
13 | response: string;
14 | conversationId: string;
15 | };
16 |
--------------------------------------------------------------------------------
/src/styles/Draggable.module.scss:
--------------------------------------------------------------------------------
1 | .draggable {
2 | cursor: move;
3 | cursor: -webkit-grab;
4 | cursor: -moz-grab;
5 | cursor: grab;
6 | }
7 |
8 | /* During drag */
9 | .draggable:active {
10 | cursor: -webkit-grabbing;
11 | cursor: -moz-grabbing;
12 | cursor: grabbing;
13 | }
14 |
15 | /* For clicking */
16 | .clickable {
17 | cursor: pointer;
18 | }
19 |
--------------------------------------------------------------------------------
/src/features/dataVisualizer/drawable/Drawable.ts:
--------------------------------------------------------------------------------
1 | export { default as ArrayDrawable } from './ArrayDrawable';
2 | export { default as ArrowDrawable } from './ArrowDrawable';
3 | export { default as BackwardArrowDrawable } from './BackwardArrowDrawable';
4 | export { default as FunctionDrawable } from './FunctionDrawable';
5 | export { default as NullDrawable } from './NullDrawable';
6 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/sideContent/resultCard.json:
--------------------------------------------------------------------------------
1 | {
2 | "timeout": "[TIMEOUT] Submission exceeded time limit for this test case.",
3 | "syntax": "[SYNTAX] Line {{line}}: {{errorExplanation}}",
4 | "runtime": "[RUNTIME] Line {{line}}: {{errorExplanation}}",
5 | "systemError": "[SYSTEM] {{errorMessage}}",
6 | "unknown": "[UNKNOWN] Autograder error: type {{errorType}}"
7 | }
8 |
--------------------------------------------------------------------------------
/src/commons/sideContent/__tests__/__snapshots__/SideContentHtmlDisplay.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`HTML Display renders correctly 1`] = `
4 |
""
9 | title="HTML Display"
10 | />
11 | `;
12 |
--------------------------------------------------------------------------------
/src/features/game/character/GameCharacterConstants.ts:
--------------------------------------------------------------------------------
1 | import { screenCenter, screenSize } from '../commons/CommonConstants';
2 |
3 | const charXOffset = 350;
4 |
5 | const CharConstants = {
6 | charWidth: 600,
7 | charRect: {
8 | x: { Left: charXOffset, Middle: screenCenter.x, Right: screenSize.x - charXOffset }
9 | }
10 | };
11 |
12 | export default CharConstants;
13 |
--------------------------------------------------------------------------------
/src/styles/ContextMenu.module.scss:
--------------------------------------------------------------------------------
1 | @import '_global';
2 |
3 | .context-menu {
4 | background-color: $cadet-color-1;
5 | padding: 5px 1px;
6 | z-index: 5;
7 | }
8 |
9 | .context-menu-item {
10 | list-style: none;
11 | user-select: none;
12 | padding: 3px 16px;
13 | white-space: nowrap;
14 |
15 | &:hover {
16 | background-color: $cadet-color-3;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/commons/utils/LocalStorageHelper.ts:
--------------------------------------------------------------------------------
1 | export const readLocalStorage = (key: string, defaultValue?: any) => {
2 | const localStorageValue = window.localStorage.getItem(key);
3 | return localStorageValue ? JSON.parse(localStorageValue) : defaultValue;
4 | };
5 |
6 | export const setLocalStorage = (key: string, value: any) => {
7 | window.localStorage.setItem(key, JSON.stringify(value));
8 | };
9 |
--------------------------------------------------------------------------------
/src/features/directory/LanguageDirectoryTypes.ts:
--------------------------------------------------------------------------------
1 | import { ILanguageDefinition } from '@sourceacademy/language-directory/dist/types';
2 |
3 | export type LanguageDirectoryState = {
4 | readonly selectedLanguageId: string | null;
5 | readonly selectedEvaluatorId: string | null;
6 | readonly languages: ILanguageDefinition[];
7 | readonly languageMap: Record;
8 | };
9 |
--------------------------------------------------------------------------------
/src/commons/featureFlags/useFeature.ts:
--------------------------------------------------------------------------------
1 | import { useTypedSelector } from '../utils/Hooks';
2 | import { FeatureFlag } from './FeatureFlag';
3 |
4 | export function useFeature(featureFlag: FeatureFlag): T {
5 | const { flagName, defaultValue } = featureFlag;
6 | const flagValue = useTypedSelector(state => state.featureFlags.modifiedFlags[flagName]);
7 | return flagValue ?? defaultValue;
8 | }
9 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/welcome.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome": {
3 | "title": "欢迎来到 {{sourceAcademyDeploymentName}}",
4 | "loggedInMessage": "您已登录为 <0>{{name}}0>。{{sourceAcademyDeploymentName}} 没有此账户的课程信息",
5 | "enrollmentMessage": "如果您已注册课程,请联系课程团队,确保您的账户已加入课程。",
6 | "resourcesForLearners": "点击<0>这里0>寻找适合您的课程。",
7 | "resourcesForEducators": "点击<0>这里0>查看入门资源列表。"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/commons/fileSystem/FileSystemActions.ts:
--------------------------------------------------------------------------------
1 | import { createAction } from '@reduxjs/toolkit';
2 | import { FSModule } from 'browserfs/dist/node/core/FS';
3 |
4 | import { SET_IN_BROWSER_FILE_SYSTEM } from './FileSystemTypes';
5 |
6 | export const setInBrowserFileSystem = createAction(
7 | SET_IN_BROWSER_FILE_SYSTEM,
8 | (inBrowserFileSystem: FSModule) => ({ payload: { inBrowserFileSystem } })
9 | );
10 |
--------------------------------------------------------------------------------
/src/features/game/mode/GameModeTypes.ts:
--------------------------------------------------------------------------------
1 | import { GamePhaseType } from '../phase/GamePhaseTypes';
2 |
3 | export enum GameMode {
4 | Move = 'Move',
5 | Explore = 'Explore',
6 | Talk = 'Talk',
7 | Menu = 'Menu'
8 | }
9 |
10 | export const gameModeToPhase = {
11 | Move: GamePhaseType.Move,
12 | Explore: GamePhaseType.Explore,
13 | Talk: GamePhaseType.Talk,
14 | Menu: GamePhaseType.Menu
15 | };
16 |
--------------------------------------------------------------------------------
/src/commons/controlBar/ControlBarResetButton.tsx:
--------------------------------------------------------------------------------
1 | import { IconNames } from '@blueprintjs/icons';
2 | import React from 'react';
3 |
4 | import ControlButton from '../ControlButton';
5 |
6 | type Props = {
7 | onClick?(): any;
8 | };
9 |
10 | export const ControlBarResetButton: React.FC = ({ onClick }) => {
11 | return ;
12 | };
13 |
--------------------------------------------------------------------------------
/src/commons/application/reducers/CommonsReducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer } from '@reduxjs/toolkit';
2 |
3 | import { updateReactRouter } from '../actions/CommonsActions';
4 | import { defaultRouter } from '../ApplicationTypes';
5 |
6 | export const RouterReducer = createReducer(defaultRouter, builder => {
7 | builder.addCase(updateReactRouter, (state, action) => {
8 | return action.payload;
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/src/features/academy/AcademyTypes.ts:
--------------------------------------------------------------------------------
1 | export const numberRegExp = /^-?\d+$/;
2 |
3 | // Full assessment path: /courses/:courseId/:assessmentType/:assessmentId?/:questionId?
4 | export const assessmentFullPathRegex = /\/courses\/\d+\/[a-zA-Z]+\/\d+\/\d+/;
5 | export const assessmentRegExp = ':assessmentId?/:questionId?';
6 |
7 | export const gradingRegExp = ':submissionId?/:questionId?';
8 | export const teamRegExp = ':teamId?';
9 |
--------------------------------------------------------------------------------
/src/features/sicp/TableOfContentsButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import ControlButton from '../../commons/ControlButton';
4 |
5 | type TableOfContentsButtonProps = {
6 | handleOpenToc: () => void;
7 | };
8 |
9 | export const TableOfContentsButton: React.FC = ({ handleOpenToc }) => {
10 | return ;
11 | };
12 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:recommended",
5 | ":dependencyDashboard",
6 | ":label(dependencies)"
7 | ],
8 | "packageRules": [
9 | {
10 | "rangeStrategy": "update-lockfile",
11 | "matchPackageNames": ["*"],
12 | "minimumReleaseAge": "5 days"
13 | }
14 | ],
15 | "enabledManagers": ["npm"]
16 | }
17 |
--------------------------------------------------------------------------------
/src/features/dataVisualizer/tree/DataTreeNode.tsx:
--------------------------------------------------------------------------------
1 | import { Data } from '../dataVisualizerTypes';
2 | import { TreeNode } from './BaseTreeNode';
3 |
4 | /**
5 | * Represents node corresponding to a data object (neither pair nor function).
6 | */
7 | export class DataTreeNode extends TreeNode {
8 | public readonly data: Data;
9 |
10 | constructor(data: Data) {
11 | super();
12 | this.data = data;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/features/game/mode/explore/GameModeExploreConstants.ts:
--------------------------------------------------------------------------------
1 | import { toS3Path } from '../../utils/GameUtils';
2 |
3 | const ExploreModeConstants = {
4 | normal: `url(${toS3Path('/ui/magnifying.png', false)}), pointer`,
5 | hover: `url(${toS3Path('/ui/magnifying_trigg.png', false)}), pointer`,
6 | checked: `url(${toS3Path('/ui/magnifying_check.png', false)}), pointer`
7 | };
8 |
9 | export default ExploreModeConstants;
10 |
--------------------------------------------------------------------------------
/src/features/game/state/GameStateConstants.ts:
--------------------------------------------------------------------------------
1 | import FontAssets from '../assets/FontAssets';
2 | import { BitmapFontStyle } from '../commons/CommonTypes';
3 |
4 | export const emptyUserState = {
5 | collectibles: [],
6 | assessments: []
7 | };
8 |
9 | export const userStateStyle: BitmapFontStyle = {
10 | key: FontAssets.zektonFont.key,
11 | size: 35,
12 | align: Phaser.GameObjects.BitmapText.ALIGN_CENTER
13 | };
14 |
--------------------------------------------------------------------------------
/src/styles/Academy.module.scss:
--------------------------------------------------------------------------------
1 | @import '_global';
2 |
3 | .Academy {
4 | height: 100%;
5 | width: 100%;
6 | display: flex;
7 | flex-direction: column;
8 | flex: 1 1 100%;
9 | }
10 |
11 | .Academy-switching-courses {
12 | height: 100%;
13 | width: 100%;
14 | display: flex;
15 | align-items: center;
16 | justify-content: center;
17 | }
18 |
19 | .listing-xp {
20 | display: flex;
21 | gap: 0.5rem;
22 | }
23 |
--------------------------------------------------------------------------------
/src/features/game/mode/sequence/GameModeSequence.ts:
--------------------------------------------------------------------------------
1 | import { IGameUI } from '../../commons/CommonTypes';
2 |
3 | /**
4 | * This is the phase that is active when none of
5 | * the mode UI's are being shown.
6 | *
7 | * It is used for dialogue/popups and notifications
8 | */
9 | class GameModeSequence implements IGameUI {
10 | public activateUI() {}
11 | public deactivateUI() {}
12 | }
13 |
14 | export default GameModeSequence;
15 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/sideContent/upload.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "通过上传类文件绕过编译器和类型检查器,在 JVM 中运行。",
3 | "acceptedFiles": "仅接受 .class 文件。当此选项卡处于活动状态时,编辑器中的代码将被忽略。",
4 | "compileCommand": "使用以下命令编译文件:",
5 | "javacCommand": "javac *.java -target 8 -source 8",
6 | "warning": "避免运行从未知来源下载的类文件。",
7 | "mainClass": "主类必须命名为 Main 并作为 Main.class 上传。",
8 | "chooseFiles": "选择文件...",
9 | "filesUploaded": "文件已上传。",
10 | "label": "上传文件"
11 | }
12 |
--------------------------------------------------------------------------------
/src/features/conductor/flagConductorEnable.ts:
--------------------------------------------------------------------------------
1 | import { createFeatureFlag } from '../../commons/featureFlags';
2 | import { featureSelector } from '../../commons/featureFlags/featureSelector';
3 |
4 | export const flagConductorEnable = createFeatureFlag(
5 | 'conductor.enable',
6 | false,
7 | 'Enables the Conductor framework for evaluation of user programs.'
8 | );
9 |
10 | export const selectConductorEnable = featureSelector(flagConductorEnable);
11 |
--------------------------------------------------------------------------------
/src/features/persistence/PersistenceActions.ts:
--------------------------------------------------------------------------------
1 | import { createActions } from 'src/commons/redux/utils';
2 |
3 | import type { PersistenceFile } from './PersistenceTypes';
4 |
5 | const PersistenceActions = createActions('persistence', {
6 | persistenceOpenPicker: true,
7 | persistenceSaveFile: (file: PersistenceFile) => file,
8 | persistenceSaveFileAs: true,
9 | persistenceInitialise: true
10 | });
11 |
12 | export default PersistenceActions;
13 |
--------------------------------------------------------------------------------
/src/commons/controlBar/ControlBarClearButton.tsx:
--------------------------------------------------------------------------------
1 | import { IconNames } from '@blueprintjs/icons';
2 | import React from 'react';
3 |
4 | import ControlButton from '../ControlButton';
5 |
6 | type Props = {
7 | handleReplOutputClear: () => void;
8 | };
9 |
10 | export const ControlBarClearButton: React.FC = ({ handleReplOutputClear }) => {
11 | return ;
12 | };
13 |
--------------------------------------------------------------------------------
/src/commons/editor/EditorHotkeys.ts:
--------------------------------------------------------------------------------
1 | export const keyBindings = {
2 | evaluate: {
3 | win: 'Shift-Enter',
4 | mac: 'Shift-Enter'
5 | },
6 | navigate: {
7 | win: 'Ctrl-B',
8 | mac: 'Command-B'
9 | },
10 | refactor: {
11 | win: 'Ctrl-M',
12 | mac: 'Command-M'
13 | },
14 | highlightScope: {
15 | win: 'Ctrl-Shift-H',
16 | mac: 'Command-Shift-H'
17 | }
18 | };
19 |
20 | export type KeyFunction = keyof typeof keyBindings;
21 |
--------------------------------------------------------------------------------
/src/pages/sicp/subcomponents/__tests__/SicpIndexPage.test.tsx:
--------------------------------------------------------------------------------
1 | import { MemoryRouter } from 'react-router';
2 | import { renderTreeJson } from 'src/commons/utils/TestUtils';
3 |
4 | import SicpIndexPage from '../../subcomponents/SicpIndexPage';
5 |
6 | test('Sicp index page', async () => {
7 | const tree = await renderTreeJson(
8 |
9 |
10 |
11 | );
12 | expect(tree).toMatchSnapshot();
13 | });
14 |
--------------------------------------------------------------------------------
/src/commons/controlBar/ControlBarCloseButton.tsx:
--------------------------------------------------------------------------------
1 | import { IconNames } from '@blueprintjs/icons';
2 | import React from 'react';
3 |
4 | import ControlButton from '../ControlButton';
5 |
6 | type ControlBarCloseButtonProps = {
7 | handleClose: () => void;
8 | };
9 |
10 | export const ControlBarCloseButton: React.FC = ({ handleClose }) => {
11 | return ;
12 | };
13 |
--------------------------------------------------------------------------------
/src/features/dataVisualizer/tree/AlreadyParsedTreeNode.ts:
--------------------------------------------------------------------------------
1 | import { TreeNode } from './BaseTreeNode';
2 | import { DrawableTreeNode } from './DrawableTreeNode';
3 |
4 | /**
5 | * Represents a node that is already parsed earlier in the tree.
6 | */
7 | export class AlreadyParsedTreeNode extends TreeNode {
8 | public actualNode: DrawableTreeNode;
9 |
10 | constructor(actualNode: DrawableTreeNode) {
11 | super();
12 | this.actualNode = actualNode;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/features/game/character/GameCharacterTypes.ts:
--------------------------------------------------------------------------------
1 | import { AssetKey, GamePosition, ItemId } from '../commons/CommonTypes';
2 |
3 | export type SpeakerDetail = {
4 | speakerId: ItemId;
5 | expression: string;
6 | speakerPosition: GamePosition;
7 | };
8 |
9 | export type Character = {
10 | id: ItemId;
11 | name: string;
12 | expressions: Map;
13 | defaultExpression: string;
14 | defaultPosition: GamePosition;
15 | scale: number;
16 | };
17 |
--------------------------------------------------------------------------------
/src/features/game/task/GameTaskTypes.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Type that encapsulates all the details of a task.
3 | * @param taskId - the id of the task
4 | * @param title - the title of the task
5 | * @param description - the description of the task
6 | * @param visible - indication of whether this task is currently visible to the user
7 | */
8 | export type TaskDetail = {
9 | taskId: string;
10 | title: string;
11 | description: string;
12 | visible: boolean;
13 | };
14 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg' {
2 | const content: string;
3 | export default content;
4 | }
5 | declare module '*.svg?react' {
6 | const ReactComponent: React.FC>;
7 | export default ReactComponent;
8 | }
9 | declare module '*.module.scss' {
10 | const content: { [className: string]: string };
11 | export default content;
12 | }
13 | declare module '*.jpg' {
14 | const content: string;
15 | export default content;
16 | }
17 |
--------------------------------------------------------------------------------
/src/features/directory/PluginDirectoryActions.ts:
--------------------------------------------------------------------------------
1 | import { IPluginDefinition } from '@sourceacademy/plugin-directory/dist/types';
2 |
3 | import { createActions } from '../../commons/redux/utils';
4 |
5 | const PluginDirectoryActions = createActions('directory/plugins', {
6 | /** Fetch plugins (saga) */
7 | fetchPlugins: null,
8 | /** Set plugins list */
9 | setPlugins: (plugins: IPluginDefinition[]) => ({ plugins })
10 | });
11 |
12 | export default PluginDirectoryActions;
13 |
--------------------------------------------------------------------------------
/src/commons/__tests__/ContentDisplay.test.tsx:
--------------------------------------------------------------------------------
1 | import ContentDisplay, { ContentDisplayProps } from '../ContentDisplay';
2 | import { renderTreeJson } from '../utils/TestUtils';
3 |
4 | const mockProps: ContentDisplayProps = {
5 | display: Test Content
6 | };
7 |
8 | test('ContentDisplay page renders correctly', async () => {
9 | const app = ;
10 | const tree = await renderTreeJson(app);
11 | expect(tree).toMatchSnapshot();
12 | });
13 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/grading.json:
--------------------------------------------------------------------------------
1 | {
2 | "gradingCsv": {
3 | "assessmentNumber": "评估编号",
4 | "assessmentName": "评估名称",
5 | "studentName": "学生姓名",
6 | "studentUsername": "学生用户名",
7 | "group": "组别",
8 | "progress": "进度",
9 | "questionCount": "问题数量",
10 | "questionsGraded": "已评分问题",
11 | "initialXp": "初始经验值",
12 | "xpAdjustment": "经验值调整",
13 | "currentXpExclBonus": "当前经验值(不含奖励)",
14 | "maxXp": "最大经验值",
15 | "bonusXp": "奖励经验值"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/commons/featureFlags/selectFeatureSaga.ts:
--------------------------------------------------------------------------------
1 | import { SagaIterator } from 'redux-saga';
2 | import { select } from 'redux-saga/effects';
3 |
4 | import { FeatureFlag } from './FeatureFlag';
5 | import { featureSelector } from './featureSelector';
6 |
7 | export function* selectFeatureSaga(featureFlag: FeatureFlag): SagaIterator {
8 | const { defaultValue } = featureFlag;
9 | const flagValue = yield select(featureSelector(featureFlag));
10 | return flagValue ?? defaultValue;
11 | }
12 |
--------------------------------------------------------------------------------
/src/features/game/quiz/GameQuizType.ts:
--------------------------------------------------------------------------------
1 | import { SpeakerDetail } from '../character/GameCharacterTypes';
2 | import { DialogueObject } from '../dialogue/GameDialogueTypes';
3 |
4 | export type Quiz = {
5 | questions: Question[];
6 | };
7 |
8 | export type Question = {
9 | question: string;
10 | prompt?: string;
11 | speaker: SpeakerDetail;
12 | answer: number;
13 | options: Option[];
14 | };
15 |
16 | export type Option = {
17 | text: string;
18 | reaction?: DialogueObject;
19 | };
20 |
--------------------------------------------------------------------------------
/src/features/sicp/utils/SicpUtils.ts:
--------------------------------------------------------------------------------
1 | import { readLocalStorage, setLocalStorage } from 'src/commons/utils/LocalStorageHelper';
2 |
3 | export const SICP_INDEX = 'index';
4 | export const SICP_CACHE_KEY = 'sicp-section';
5 |
6 | export const setSicpSectionLocalStorage = (value: string) => {
7 | setLocalStorage(SICP_CACHE_KEY, value);
8 | };
9 |
10 | export const readSicpSectionLocalStorage = () => {
11 | const data = readLocalStorage(SICP_CACHE_KEY, SICP_INDEX);
12 | return data;
13 | };
14 |
--------------------------------------------------------------------------------
/src/features/directory/flagDirectoryLanguageEnable.ts:
--------------------------------------------------------------------------------
1 | import { createFeatureFlag } from '../../commons/featureFlags';
2 | import { featureSelector } from '../../commons/featureFlags/featureSelector';
3 |
4 | export const flagDirectoryLanguageEnable = createFeatureFlag(
5 | 'directory.language.enable',
6 | false,
7 | 'Enable new language directory powered selection UI and runtime selection.'
8 | );
9 |
10 | export const selectDirectoryLanguageEnable = featureSelector(flagDirectoryLanguageEnable);
11 |
--------------------------------------------------------------------------------
/src/features/playground/PlaygroundTypes.ts:
--------------------------------------------------------------------------------
1 | import { SALanguage } from 'src/commons/application/ApplicationTypes';
2 |
3 | import { GitHubSaveInfo } from '../github/GitHubTypes';
4 | import { PersistenceFile } from '../persistence/PersistenceTypes';
5 |
6 | export type PlaygroundState = {
7 | readonly queryString?: string;
8 | readonly shortURL?: string;
9 | readonly persistenceFile?: PersistenceFile;
10 | readonly githubSaveInfo: GitHubSaveInfo;
11 | readonly languageConfig: SALanguage;
12 | };
13 |
--------------------------------------------------------------------------------
/src/commons/controlBar/ControlBarSubmit.tsx:
--------------------------------------------------------------------------------
1 | import { IconNames } from '@blueprintjs/icons';
2 | import React from 'react';
3 |
4 | import ControlButton from '../ControlButton';
5 |
6 | type Props = {
7 | onClick?(): any;
8 | };
9 |
10 | export const ControlBarSubmit: React.FC = ({ onClick }) => {
11 | return (
12 |
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/src/features/sourceRecorder/sourcereel/SourcereelTypes.ts:
--------------------------------------------------------------------------------
1 | import { WorkspaceState } from '../../../commons/workspace/WorkspaceTypes';
2 | import { PlaybackData, RecordingStatus } from '../SourceRecorderTypes';
3 |
4 | type SourcereelWorkspaceAttr = {
5 | readonly playbackData: PlaybackData;
6 | readonly recordingStatus: RecordingStatus;
7 | readonly timeElapsedBeforePause: number;
8 | readonly timeResumed: number;
9 | };
10 | export type SourcereelWorkspaceState = SourcereelWorkspaceAttr & WorkspaceState;
11 |
--------------------------------------------------------------------------------
/src/styles/ConfigureControls.module.scss:
--------------------------------------------------------------------------------
1 | /* Container for warnings upon clicking assign entries button */
2 | .reassign-voting-warning {
3 | font-size: 11px;
4 | margin-left: 38px;
5 | }
6 |
7 | .confirm-assign-voting,
8 | .current-voting-status {
9 | max-height: 30px;
10 | display: flex;
11 | align-items: center;
12 | gap: 6px;
13 | margin-left: 15px;
14 |
15 | .confirm-assign-text {
16 | margin-top: 9px;
17 | }
18 |
19 | .voting-status-text {
20 | margin-top: 10px;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/features/teamFormation/TeamFormationTypes.ts:
--------------------------------------------------------------------------------
1 | import { AssessmentType } from '../../commons/assessment/AssessmentTypes';
2 |
3 | /**
4 | * Information on a Team, for a particular student submission
5 | * for a particular assessment. Used for display in the Team Formation Table.
6 | */
7 | export type TeamFormationOverview = {
8 | teamId: number;
9 | assessmentId: number;
10 | assessmentName: string;
11 | assessmentType: AssessmentType;
12 | studentIds: number[];
13 | studentNames: string[];
14 | };
15 |
--------------------------------------------------------------------------------
/src/pages/sicp/subcomponents/__tests__/SicpToc.test.tsx:
--------------------------------------------------------------------------------
1 | import { MemoryRouter } from 'react-router';
2 | import { renderTreeJson } from 'src/commons/utils/TestUtils';
3 |
4 | import SicpToc from '../SicpToc';
5 |
6 | test('Sicp toc renders correctly', async () => {
7 | const props = {
8 | handleCloseToc: () => {}
9 | };
10 |
11 | const tree = await renderTreeJson(
12 |
13 |
14 |
15 | );
16 | expect(tree).toMatchSnapshot();
17 | });
18 |
--------------------------------------------------------------------------------
/public/assets/mockAwardsMapping.txt:
--------------------------------------------------------------------------------
1 | collectibles
2 | cookies
3 | cookies-award, /images/cookies.png, Cookies, Cookies are delicious!
4 | computer
5 | computer-award, /objects/cmd-monitor03/normal.png, Computer, Computer is advanced technology
6 |
7 | achievements
8 | 1
9 | place-award, /images/2d-2018-1st-place.png, Rune Master, This is an award you get for obtaining rune master
10 | 2
11 | popular-award, /images/2d-2018-1st-popular.png, Popular, Wow, Mr. Popular! :)
12 |
13 |
--------------------------------------------------------------------------------
/src/features/dashboard/DashboardTypes.ts:
--------------------------------------------------------------------------------
1 | export type DashboardState = {
2 | gradingSummary: GradingSummary;
3 | };
4 |
5 | /**
6 | * As we are dynamically rendering the ag-grid table based on the number of assessment types in
7 | * the course, we cannot properly type the fields in GradingSummary.
8 | *
9 | * In short, cols contains the keys to each object in rows, and corresponds to the display order of ag-grid columns.
10 | */
11 | export type GradingSummary = {
12 | cols: string[];
13 | rows: object[];
14 | };
15 |
--------------------------------------------------------------------------------
/src/styles/_contributors.scss:
--------------------------------------------------------------------------------
1 | // Mixins
2 |
3 | @mixin mQ($arg) {
4 | @media screen and (max-width: $arg) {
5 | @content;
6 | }
7 | }
8 |
9 | // Overall fullpage
10 |
11 | .fullpage {
12 | margin-top: 20px;
13 | margin-bottom: 20px;
14 | text-align: center;
15 | width: 100%;
16 |
17 | .fullpage-content {
18 | padding: 10px 20px 10px 20px;
19 | display: inline-block;
20 | margin: 0 0 10px 0;
21 | width: 80%;
22 | @include mQ(750px) {
23 | width: 90%;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/styles/_sourcereel.scss:
--------------------------------------------------------------------------------
1 | .Sourcereel {
2 | height: 100%;
3 | display: flex;
4 | background-color: $cadet-color-1;
5 | flex-direction: column;
6 | flex: 1 1 100%;
7 | }
8 |
9 | .Timer {
10 | display: flex;
11 | width: 80%;
12 | justify-content: center;
13 | margin: 0 auto;
14 | }
15 |
16 | .RecorderControl {
17 | display: flex;
18 | width: 95%;
19 | justify-content: center;
20 | margin: 0 auto;
21 | }
22 |
23 | .Multi-line {
24 | white-space: 'pre-line';
25 | overflow-wrap: 'break-word';
26 | }
27 |
--------------------------------------------------------------------------------
/src/commons/utils/__tests__/GitHubPersistenceHelper.test.ts:
--------------------------------------------------------------------------------
1 | import { generateOctokitInstance } from '../GitHubPersistenceHelper';
2 |
3 | test('Octokit instance is generated with input auth value', async () => {
4 | const authToken = '123456789abcdefghijklmnopqrstuvwxyz';
5 | const generatedOctokitInstance = generateOctokitInstance(authToken);
6 |
7 | const authObject = (await generatedOctokitInstance.auth()) as any;
8 | expect(authObject.token).toBe(authToken);
9 | expect(authObject.tokenType).toBe('oauth');
10 | });
11 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/sideContent/substVisualizer.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome": "欢迎来到步骤器!",
3 | "instructions": "在此标签页中,REPL 将被隐藏,因此请确保您的代码在运行步骤器之前没有错误。您可以通过在左侧编写程序,然后拖动上方的滑块来查看其评估过程。",
4 | "evaluationSteps": "在偶数步骤中,将要评估的程序部分会以黄色突出显示。在奇数步骤中,评估结果会以绿色突出显示。您可以在控制栏中更改最大步骤限制(500-5000,默认值为1000)。",
5 | "shortcutsTitle": "一些有用的键盘快捷键:",
6 | "shortcuts": {
7 | "a": "a: 跳到第一步",
8 | "e": "e: 跳到最后一步",
9 | "f": "f: 跳到下一步",
10 | "b": "b: 跳到上一步"
11 | },
12 | "shortcutsNote": "请注意,这些快捷键仅在浏览器焦点位于此标签页时有效(点击说明文字或其上方区域)。"
13 | }
14 |
--------------------------------------------------------------------------------
/src/commons/fileSystemView/FileSystemViewIndentationPadding.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | type Props = {
4 | indentationLevel: number;
5 | };
6 |
7 | const SPACING_PER_LEVEL = 19;
8 |
9 | const FileSystemViewIndentationPadding: React.FC = ({ indentationLevel }) => {
10 | const indentationStyle: React.CSSProperties = {
11 | paddingLeft: `${SPACING_PER_LEVEL * indentationLevel}px`
12 | };
13 |
14 | return ;
15 | };
16 |
17 | export default FileSystemViewIndentationPadding;
18 |
--------------------------------------------------------------------------------
/src/features/game/location/GameMapHelper.ts:
--------------------------------------------------------------------------------
1 | import { GameLocation } from './GameMapTypes';
2 |
3 | /**
4 | * Intialises an an empty location
5 | */
6 | export function createEmptyLocation(): GameLocation {
7 | return {
8 | id: '',
9 | name: '',
10 | assetKey: '',
11 | previewKey: null,
12 | modes: new Set([]),
13 | navigation: new Set([]),
14 | talkTopics: new Set([]),
15 | objects: new Set([]),
16 | boundingBoxes: new Set([]),
17 | bgmKey: '',
18 | characters: new Set([])
19 | };
20 | }
21 |
--------------------------------------------------------------------------------
/src/commons/utils/MemoizeHelper.ts:
--------------------------------------------------------------------------------
1 | import * as _ from 'lodash';
2 |
3 | /**
4 | * Performs a deep comparison between the previous & next props state
5 | * to determine if they are equivalent. For use with the `React.memo`
6 | * higher order component.
7 | *
8 | * @param prevProps The previous state of the props passed into a component.
9 | * @param nextProps The next state of the props passed into a component
10 | */
11 | export const propsAreEqual = (prevProps: T, nextProps: T): boolean =>
12 | _.isEqual(prevProps, nextProps);
13 |
--------------------------------------------------------------------------------
/src/features/game/mode/talk/GameModeTalkConstants.ts:
--------------------------------------------------------------------------------
1 | import FontAssets from '../../assets/FontAssets';
2 | import { screenSize } from '../../commons/CommonConstants';
3 | import { BitmapFontStyle } from '../../commons/CommonTypes';
4 |
5 | export const talkButtonStyle: BitmapFontStyle = {
6 | key: FontAssets.zektonFont.key,
7 | size: 30,
8 | align: Phaser.GameObjects.BitmapText.ALIGN_CENTER
9 | };
10 |
11 | const TalkModeConstants = {
12 | button: { ySpace: screenSize.y * 0.7 }
13 | };
14 |
15 | export default TalkModeConstants;
16 |
--------------------------------------------------------------------------------
/src/commons/__tests__/__snapshots__/ContentDisplay.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`ContentDisplay page renders correctly 1`] = `
4 |
7 |
10 |
13 |
14 | Test Content
15 |
16 |
17 |
18 |
19 | `;
20 |
--------------------------------------------------------------------------------
/src/commons/utils/SourcerorHelper.ts:
--------------------------------------------------------------------------------
1 | import { Context } from 'js-slang/dist/types';
2 |
3 | import InterpreterActions from '../application/actions/InterpreterActions';
4 |
5 | export function makeExternalBuiltins(context: Context): any {
6 | return {
7 | display: (v: string) => {
8 | if (typeof (window as any).__REDUX_STORE__ !== 'undefined') {
9 | (window as any).__REDUX_STORE__.dispatch(
10 | InterpreterActions.handleConsoleLog(context.externalContext, v)
11 | );
12 | }
13 | }
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/src/features/conductor/flagConductorEvaluatorUrl.ts:
--------------------------------------------------------------------------------
1 | import { createFeatureFlag } from '../../commons/featureFlags';
2 | import { featureSelector } from '../../commons/featureFlags/featureSelector';
3 |
4 | export const flagConductorEvaluatorUrl = createFeatureFlag(
5 | 'conductor.evaluator.url',
6 | 'https://fyp.tsammeow.dev/evaluator/0.2.1/worker.js',
7 | 'The URL where Conductor may find the Runner to be used for running user programs.'
8 | );
9 |
10 | export const selectConductorEvaluatorUrl = featureSelector(flagConductorEvaluatorUrl);
11 |
--------------------------------------------------------------------------------
/src/commons/controlBar/ControlBarReturnToAcademyButton.tsx:
--------------------------------------------------------------------------------
1 | import { IconNames } from '@blueprintjs/icons';
2 | import React from 'react';
3 |
4 | import ControlButton from '../ControlButton';
5 |
6 | type Props = {
7 | onClick?(): any;
8 | };
9 |
10 | export const ControlBarReturnToAcademyButton: React.FC = ({ onClick }) => {
11 | return (
12 |
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/src/commons/application/actions/CommonsActions.ts:
--------------------------------------------------------------------------------
1 | import { Router } from 'src/commons/application/types/CommonsTypes';
2 | import { createActions } from 'src/commons/redux/utils';
3 |
4 | const CommonsActions = createActions('commons', {
5 | logOut: () => ({}),
6 | updateReactRouter: (updatedRouter: Router) => updatedRouter
7 | });
8 |
9 | // For compatibility with existing code (reducer)
10 | export const { logOut, updateReactRouter } = CommonsActions;
11 |
12 | // For compatibility with existing code (actions helper)
13 | export default CommonsActions;
14 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/sideContent/contestLeaderboard.json:
--------------------------------------------------------------------------------
1 | {
2 | "noEntries": "未找到符合条件的比赛排行榜条目。",
3 | "titles": {
4 | "score": "得分排行榜",
5 | "popularVote": "人气投票排行榜",
6 | "default": "比赛排行榜"
7 | },
8 | "tooltips": {
9 | "score": "查看得分最高的比赛条目!",
10 | "popularVote": "查看最受欢迎的比赛条目!",
11 | "default": "查看评分最高的比赛条目!"
12 | },
13 | "headers": {
14 | "studentName": "学生姓名",
15 | "rank": "排名",
16 | "score": {
17 | "calculated": "计算得分",
18 | "popularity": "人气得分",
19 | "default": "指标"
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/commons/fileSystem/__tests__/FileSystemActions.test.ts:
--------------------------------------------------------------------------------
1 | import { BFSRequire } from 'browserfs';
2 |
3 | import { setInBrowserFileSystem } from '../FileSystemActions';
4 | import { SET_IN_BROWSER_FILE_SYSTEM } from '../FileSystemTypes';
5 |
6 | test('setInBrowserFileSystem generates correct action object', () => {
7 | const inBrowserFileSystem = BFSRequire('fs');
8 | const action = setInBrowserFileSystem(inBrowserFileSystem);
9 | expect(action).toEqual({
10 | type: SET_IN_BROWSER_FILE_SYSTEM,
11 | payload: { inBrowserFileSystem }
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/src/features/game/popUp/GamePopUpConstants.ts:
--------------------------------------------------------------------------------
1 | import { screenCenter, screenSize } from '../commons/CommonConstants';
2 |
3 | const popUpXOffset = 400;
4 |
5 | const PopUpConstants = {
6 | image: { xOffset: 20, yOffset: 20 },
7 | rect: {
8 | x: { Left: popUpXOffset, Middle: screenCenter.x, Right: screenSize.x - popUpXOffset },
9 | y: { Small: 325, Medium: 350, Large: 420 },
10 | scale: { Small: 0.7, Medium: 1, Large: 1.5 },
11 | width: 280,
12 | height: 280
13 | },
14 | tweenDuration: 300
15 | };
16 |
17 | export default PopUpConstants;
18 |
--------------------------------------------------------------------------------
/src/commons/application/__tests__/__snapshots__/Application.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Application renders correctly 1`] = `
4 |
14 |
24 |
25 | `;
26 |
--------------------------------------------------------------------------------
/src/commons/navigationBar/subcomponents/__tests__/SicpNavigationBar.test.tsx:
--------------------------------------------------------------------------------
1 | import { shallowRender } from 'src/commons/utils/TestUtils';
2 | import { vi } from 'vitest';
3 |
4 | import SicpNavigationBar from '../SicpNavigationBar';
5 |
6 | vi.mock('react-router', () => ({
7 | useParams: vi.fn().mockReturnValue({ section: 'index' }),
8 | useNavigate: vi.fn().mockReturnValue(vi.fn())
9 | }));
10 |
11 | test('Navbar renders correctly', () => {
12 | const navbar = ;
13 | const tree = shallowRender(navbar);
14 | expect(tree).toMatchSnapshot();
15 | });
16 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/sideContent/dataVisualizer.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultText": "The data visualizer helps you to visualize data structures.",
3 | "instructions": "It is activated by calling the function <0/>, where <1/> would be the <2/> data structure that you want to visualize and <3/> is the number of structures.",
4 | "reference": "The data visualizer uses box-and-pointer diagrams, as introduced in <0>Structure and Interpretation of Computer Programs, JavaScript Edition, Chapter 2, Section 20>.",
5 | "label": "Data Visualizer",
6 | "previous": "Previous",
7 | "next": "Next"
8 | }
9 |
--------------------------------------------------------------------------------
/src/commons/assessment/AssessmentNotFound.tsx:
--------------------------------------------------------------------------------
1 | import { Classes, NonIdealState } from '@blueprintjs/core';
2 | import { IconNames } from '@blueprintjs/icons';
3 | import classNames from 'classnames';
4 |
5 | const AssessmentNotFound: React.FC = () => (
6 |
7 |
12 |
13 | );
14 |
15 | export default AssessmentNotFound;
16 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/grading.json:
--------------------------------------------------------------------------------
1 | {
2 | "gradingCsv": {
3 | "assessmentNumber": "Assessment Number",
4 | "assessmentName": "Assessment Name",
5 | "studentName": "Student Name",
6 | "studentUsername": "Student Username",
7 | "group": "Group",
8 | "progress": "Progress",
9 | "questionCount": "Question Count",
10 | "questionsGraded": "Questions Graded",
11 | "initialXp": "Initial XP",
12 | "xpAdjustment": "XP Adjustment",
13 | "currentXpExclBonus": "Current XP (excl. bonus)",
14 | "maxXp": "Max XP",
15 | "bonusXp": "Bonus XP"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/sideContent/autograder.json:
--------------------------------------------------------------------------------
1 | {
2 | "noTestcases": "此问题没有提供测试用例。",
3 | "noResults": "没有结果可显示。",
4 | "testcases": "测试用例",
5 | "results": "自动评分结果",
6 | "tooltip": {
7 | "clickTestcase": "点击下面的每个测试用例以使用编辑器中的程序执行它。",
8 | "executeAll": "要一次性执行所有测试用例,请在此选项卡处于活动状态时评估编辑器中的程序。",
9 | "backgroundInfo": "绿色或红色背景分别表示测试用例通过或失败。",
10 | "privateTestcases": "私有测试用例(仅在评分时对工作人员可见)具有灰色背景。"
11 | },
12 | "headers": {
13 | "testcase": "测试用例",
14 | "expected": "预期结果",
15 | "actual": "实际结果",
16 | "sn": "序号",
17 | "status": "测试用例状态"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/welcome.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome": {
3 | "title": "Welcome to {{sourceAcademyDeploymentName}}",
4 | "loggedInMessage": "You have logged in as <0>{{name}}0>. {{sourceAcademyDeploymentName}} does not have any course information for this account",
5 | "enrollmentMessage": "If you are enrolled in a course, check with the course staff to make sure your account is added to the course.",
6 | "resourcesForLearners": "Click <0>here0> to find a course that suits your needs.",
7 | "resourcesForEducators": "Click <0>here0> for a list of resources to get started."
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/features/dashboard/DashboardActions.ts:
--------------------------------------------------------------------------------
1 | import { createActions } from 'src/commons/redux/utils';
2 |
3 | import { GradingSummary } from './DashboardTypes';
4 |
5 | const DashboardActions = createActions('dashboard', {
6 | fetchGroupGradingSummary: () => ({}),
7 | updateGroupGradingSummary: (gradingSummary: GradingSummary) => gradingSummary
8 | });
9 |
10 | // For compatibility with existing code (reducer)
11 | export const { fetchGroupGradingSummary, updateGroupGradingSummary } = DashboardActions;
12 |
13 | // For compatibility with existing code (actions helper)
14 | export default DashboardActions;
15 |
--------------------------------------------------------------------------------
/src/features/playground/__tests__/PlaygroundReducer.test.ts:
--------------------------------------------------------------------------------
1 | import { defaultPlayground } from '../../../commons/application/ApplicationTypes';
2 | import PlaygroundActions from '../PlaygroundActions';
3 | import { PlaygroundReducer } from '../PlaygroundReducer';
4 |
5 | test('CHANGE_QUERY_STRING sets queryString correctly ', () => {
6 | const action = {
7 | type: PlaygroundActions.changeQueryString.type,
8 | payload: 'hello world'
9 | } as const;
10 | expect(PlaygroundReducer(defaultPlayground, action)).toEqual({
11 | ...defaultPlayground,
12 | queryString: action.payload
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/src/commons/sagas/FeatureFlagSaga.ts:
--------------------------------------------------------------------------------
1 | import { call } from 'redux-saga/effects';
2 |
3 | import { FeatureFlagsActions } from '../featureFlags';
4 | import { combineSagaHandlers } from '../redux/utils';
5 |
6 | const FeatureFlagSaga = combineSagaHandlers({
7 | [FeatureFlagsActions.setFlag.type]: function* ({ payload: { featureFlag, value } }) {
8 | yield call([featureFlag, 'onChange'], value);
9 | },
10 | [FeatureFlagsActions.resetFlag.type]: function* ({ payload: { featureFlag } }) {
11 | yield call([featureFlag, 'onChange'], featureFlag.defaultValue);
12 | }
13 | });
14 |
15 | export default FeatureFlagSaga;
16 |
--------------------------------------------------------------------------------
/src/features/game/input/GameInputConstants.ts:
--------------------------------------------------------------------------------
1 | export const keyboardShortcuts = {
2 | Dashboard: Phaser.Input.Keyboard.KeyCodes.TAB,
3 | Menu: Phaser.Input.Keyboard.KeyCodes.ESC,
4 | Next: Phaser.Input.Keyboard.KeyCodes.SPACE,
5 | Notif: Phaser.Input.Keyboard.KeyCodes.SPACE,
6 | Explore: Phaser.Input.Keyboard.KeyCodes.E,
7 | Move: Phaser.Input.Keyboard.KeyCodes.V,
8 | Talk: Phaser.Input.Keyboard.KeyCodes.T,
9 | Options: [
10 | Phaser.Input.Keyboard.KeyCodes.ONE,
11 | Phaser.Input.Keyboard.KeyCodes.TWO,
12 | Phaser.Input.Keyboard.KeyCodes.THREE,
13 | Phaser.Input.Keyboard.KeyCodes.FOUR
14 | ]
15 | };
16 |
--------------------------------------------------------------------------------
/src/commons/controlBar/ControlBarQuestionViewButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import ControlButton from '../ControlButton';
4 |
5 | /**
6 | * @prop questionProgress a tuple of (current question number, question length) where
7 | * the current question number is 1-based.
8 | */
9 | type Props = {
10 | questionProgress: [number, number] | null;
11 | };
12 |
13 | export const ControlBarQuestionViewButton: React.FC = ({ questionProgress }) => {
14 | return (
15 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/src/i18n/i18n.ts:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import LanguageDetector from 'i18next-browser-languagedetector';
3 | import { initReactI18next } from 'react-i18next';
4 |
5 | import { i18nLanguageCode, resources } from './locales';
6 |
7 | const isDevelopment = process.env.NODE_ENV === 'development';
8 |
9 | i18n
10 | .use(LanguageDetector)
11 | .use(initReactI18next)
12 | .init({
13 | fallbackLng: i18nLanguageCode.ENGLISH,
14 | debug: isDevelopment,
15 | resources,
16 | interpolation: {
17 | escapeValue: false // not needed for react as it escapes by default
18 | }
19 | });
20 |
21 | export default i18n;
22 |
--------------------------------------------------------------------------------
/src/commons/delay/Delay.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | type Props = {
4 | children: JSX.Element;
5 | waitInMsBeforeRender: number;
6 | };
7 |
8 | /**
9 | * Delays the rendering of child components by a set time.
10 | */
11 | const Delay: React.FC = ({ children, waitInMsBeforeRender }) => {
12 | const [isRendered, setIsRendered] = React.useState(false);
13 |
14 | React.useEffect(() => {
15 | const timeoutId = setTimeout(() => setIsRendered(true), waitInMsBeforeRender);
16 | return () => clearTimeout(timeoutId);
17 | }, [waitInMsBeforeRender]);
18 |
19 | return isRendered ? children : <>>;
20 | };
21 |
22 | export default Delay;
23 |
--------------------------------------------------------------------------------
/src/features/dashboard/DashboardReducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer, Reducer } from '@reduxjs/toolkit';
2 |
3 | import { defaultDashboard } from '../../commons/application/ApplicationTypes';
4 | import { SourceActionType } from '../../commons/utils/ActionsHelper';
5 | import { updateGroupGradingSummary } from './DashboardActions';
6 | import { DashboardState } from './DashboardTypes';
7 |
8 | export const DashboardReducer: Reducer = createReducer(
9 | defaultDashboard,
10 | builder => {
11 | builder.addCase(updateGroupGradingSummary, (state, action) => {
12 | state.gradingSummary = action.payload;
13 | });
14 | }
15 | );
16 |
--------------------------------------------------------------------------------
/src/commons/fileSystem/FileSystemReducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer, Reducer } from '@reduxjs/toolkit';
2 |
3 | import { defaultFileSystem } from '../application/ApplicationTypes';
4 | import { SourceActionType } from '../utils/ActionsHelper';
5 | import { setInBrowserFileSystem } from './FileSystemActions';
6 | import { FileSystemState } from './FileSystemTypes';
7 |
8 | export const FileSystemReducer: Reducer = createReducer(
9 | defaultFileSystem,
10 | builder => {
11 | builder.addCase(setInBrowserFileSystem, (state, action) => {
12 | state.inBrowserFileSystem = action.payload.inBrowserFileSystem;
13 | });
14 | }
15 | );
16 |
--------------------------------------------------------------------------------
/src/features/game/boundingBoxes/GameBoundingBoxTypes.ts:
--------------------------------------------------------------------------------
1 | import { IGameActionable } from '../action/GameActionTypes';
2 | import { TrackInteraction } from '../commons/CommonTypes';
3 |
4 | /**
5 | * Information on a bounding box, a clickable rectangle area
6 | *
7 | * @prop {number} x - x coordinate of center of bounding box
8 | * @prop {number} y - y coordinate of center of bounding box
9 | * @prop {number} width - width of bounding box
10 | * @prop {number} height - height of bounding box
11 | */
12 | export type BBoxProperty = TrackInteraction &
13 | IGameActionable & {
14 | x: number;
15 | y: number;
16 | width: number;
17 | height: number;
18 | };
19 |
--------------------------------------------------------------------------------
/src/features/conductor/createConductor.ts:
--------------------------------------------------------------------------------
1 | import { Conduit, IConduit } from '@sourceacademy/conductor/dist/conduit';
2 |
3 | import { BrowserHostPlugin } from './BrowserHostPlugin';
4 |
5 | export function createConductor(
6 | evaluatorPath: string,
7 | onRequestFile: (fileName: string) => Promise,
8 | onRequestLoadPlugin: (pluginName: string) => void
9 | ): { hostPlugin: BrowserHostPlugin; conduit: IConduit } {
10 | const worker = new Worker(evaluatorPath);
11 | const conduit = new Conduit(worker, true);
12 | const hostPlugin = conduit.registerPlugin(BrowserHostPlugin, onRequestFile, onRequestLoadPlugin);
13 | return { hostPlugin, conduit };
14 | }
15 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/sideContent/upload.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Bypass the compiler and type checker by uploading class files to run in the JVM.",
3 | "acceptedFiles": "Only .class files are accepted. Code in the editor will be ignored when running while this tab is active.",
4 | "compileCommand": "Compile the files with the following command:",
5 | "javacCommand": "javac *.java -target 8 -source 8",
6 | "warning": "Avoid running class files downloaded from unknown sources.",
7 | "mainClass": "Main class must be named Main and uploaded as Main.class.",
8 | "chooseFiles": "Choose files...",
9 | "filesUploaded": "file(s) uploaded.",
10 | "label": "Upload files"
11 | }
12 |
--------------------------------------------------------------------------------
/src/commons/editor/__tests__/__snapshots__/Editor.test.tsx.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`Editor renders correctly 1`] = `
4 |
20 | `;
21 |
--------------------------------------------------------------------------------
/src/commons/utils/ConsoleOverload.ts:
--------------------------------------------------------------------------------
1 | import { stringify } from 'js-slang/dist/utils/stringify';
2 |
3 | type DisplayBufferCallback = (log: string) => void;
4 |
5 | type ConsoleOverloadMethod = (bufferCallback: DisplayBufferCallback) => (args: T) => void;
6 |
7 | // TODO add other overloads
8 | // - e.g. "warn"/"debug"/"time"/"timeEnd"
9 | interface ConsoleOverload {
10 | log: ConsoleOverloadMethod;
11 | }
12 |
13 | export const consoleOverloads: ConsoleOverload = {
14 | log:
15 | (bufferCallback: DisplayBufferCallback) =>
16 | (...args: any[]) => {
17 | bufferCallback(args.map(log => (typeof log === 'string' ? log : stringify(log))).join(' '));
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/src/commons/achievement/view/AchievementViewCompletion.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | type Props = {
4 | awardedXp: number;
5 | completionText: string;
6 | };
7 |
8 | const AchievementViewCompletion: React.FC = ({ awardedXp, completionText }) => {
9 | const paragraphs = completionText ? completionText.split('\n') : [''];
10 |
11 | return (
12 |
13 | {awardedXp > 0 &&
{`AWARDED ${awardedXp}XP`}
}
14 | {paragraphs.map((para, idx) => (
15 |
16 | {para}
17 |
18 |
19 | ))}
20 |
21 | );
22 | };
23 |
24 | export default AchievementViewCompletion;
25 |
--------------------------------------------------------------------------------
/src/commons/controlBar/ControlBarPreviousButton.tsx:
--------------------------------------------------------------------------------
1 | import { IconNames } from '@blueprintjs/icons';
2 | import React from 'react';
3 |
4 | import ControlButton from '../ControlButton';
5 |
6 | type ControlBarPreviousButtonProps = DispatchProps & StateProps;
7 |
8 | type DispatchProps = {
9 | onClick?(): any;
10 | };
11 |
12 | type StateProps = {
13 | key: string;
14 | questionProgress: [number, number] | null;
15 | };
16 |
17 | export const ControlBarPreviousButton: React.FC = props => {
18 | return props.questionProgress![0] <= 1 ? null : (
19 |
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/src/commons/utils/GitHubPersistenceHelper.ts:
--------------------------------------------------------------------------------
1 | import { Octokit } from '@octokit/rest';
2 |
3 | /**
4 | * Returns an instance to Octokit created using the authentication token
5 | */
6 | export function generateOctokitInstance(authToken: string) {
7 | const octokit = new Octokit({
8 | auth: authToken,
9 | userAgent: 'Source Academy Playground',
10 | baseUrl: 'https://api.github.com',
11 | log: {
12 | debug: () => {},
13 | info: () => {},
14 | warn: console.warn,
15 | error: console.error
16 | },
17 | request: {
18 | agent: undefined,
19 | fetch: undefined,
20 | timeout: 0
21 | }
22 | });
23 |
24 | return octokit;
25 | }
26 |
--------------------------------------------------------------------------------
/src/commons/utils/QueryHelper.ts:
--------------------------------------------------------------------------------
1 | import qs from 'query-string';
2 |
3 | export interface IParsedQuery {
4 | [key: string]: string;
5 | }
6 |
7 | /**
8 | * Parse a query string into an object.
9 | *
10 | * This is a wrapper for query-string that disables array and null parsing (so
11 | * the object has only strings).
12 | */
13 | export function parseQuery(query: string): IParsedQuery {
14 | const parsed = qs.parse(query);
15 | for (const [key, val] of Object.entries(parsed)) {
16 | if (Array.isArray(val)) {
17 | parsed[key] = val.join(',');
18 | } else if (val === null) {
19 | delete parsed[key];
20 | }
21 | }
22 |
23 | return parsed as IParsedQuery;
24 | }
25 |
--------------------------------------------------------------------------------
/src/features/gameSimulator/GameSimulatorUtils.ts:
--------------------------------------------------------------------------------
1 | export const createHeadersWithCors = (): Headers => {
2 | const headers = new Headers();
3 | headers.append('Access-Control-Allow-Origin', '*');
4 | return headers;
5 | };
6 |
7 | export const loadFileLocally = (storageName: string, txtFile: File) => {
8 | const reader = new FileReader();
9 | reader.readAsText(txtFile);
10 | reader.onloadend = _ => {
11 | if (!reader.result) {
12 | return;
13 | }
14 | sessionStorage.setItem(storageName, reader.result.toString());
15 | };
16 | };
17 |
18 | export const dateOneYearFromNow = (date: Date) => {
19 | date.setFullYear(date.getFullYear() + 1);
20 | return date;
21 | };
22 |
--------------------------------------------------------------------------------
/src/features/playground/__tests__/PlaygroundActions.test.ts:
--------------------------------------------------------------------------------
1 | import PlaygroundActions from '../PlaygroundActions';
2 |
3 | test('generateLzString generates correct action object', () => {
4 | const action = PlaygroundActions.generateLzString();
5 | expect(action).toEqual({
6 | type: PlaygroundActions.generateLzString.type,
7 | payload: {}
8 | });
9 | });
10 |
11 | test('changeQueryString generates correct action object', () => {
12 | const queryString = 'test-query-string';
13 | const action = PlaygroundActions.changeQueryString(queryString);
14 | expect(action).toEqual({
15 | type: PlaygroundActions.changeQueryString.type,
16 | payload: queryString
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/src/commons/notificationBadge/NotificationBadgeTypes.ts:
--------------------------------------------------------------------------------
1 | export type Notification = {
2 | assessment_id?: number;
3 | assessment_type?: string;
4 | assessment_title?: string;
5 | id: number;
6 | submission_id?: number;
7 | type: NotificationType;
8 | };
9 |
10 | export enum NotificationTypes {
11 | new = 'new',
12 | submitted = 'submitted',
13 | unsubmitted = 'unsubmitted',
14 | published_grading = 'published_grading',
15 | unpublished_grading = 'unpublished_grading',
16 | new_message = 'new_message'
17 | }
18 |
19 | export type NotificationType = keyof typeof NotificationTypes;
20 |
21 | export type NotificationFilterFunction = (notifications: Notification[]) => Notification[];
22 |
--------------------------------------------------------------------------------
/src/features/game/chapter/GameChapterMocks.ts:
--------------------------------------------------------------------------------
1 | import { GameChapter } from './GameChapterTypes';
2 |
3 | const GameChapterMocks: GameChapter[] = [
4 | {
5 | title: 'Spaceship Emergency',
6 | imageUrl: '/locations/planet-y-orbit/crashing.png',
7 | filenames: ['../../assets/mockChapter0.txt', '../../assets/mockChapter0.1.txt']
8 | },
9 | {
10 | title: 'Alien Attack',
11 | imageUrl: '/locations/telebay/emergency.png',
12 | filenames: ['../../assets/mockChapter1.txt']
13 | },
14 | {
15 | title: 'Jedi Master',
16 | imageUrl: '/locations/classroom/normal.png',
17 | filenames: ['../../assets/mockChapter2.txt']
18 | }
19 | ];
20 | export default GameChapterMocks;
21 |
--------------------------------------------------------------------------------
/src/styles/_gamesimulator.scss:
--------------------------------------------------------------------------------
1 | #simulator-display {
2 | width: 200px;
3 | align-self: flex-start;
4 |
5 | #game-display > canvas {
6 | width: 600px;
7 | }
8 | }
9 |
10 | .GameSimulatorWrapper {
11 | display: flex;
12 | flex-direction: row;
13 | margin: 30px;
14 | border-radius: 10px;
15 | overflow: hidden;
16 | }
17 |
18 | #asset-display {
19 | width: 200px;
20 | align-self: flex-end;
21 | background-color: white;
22 | }
23 |
24 | .LeftAlign {
25 | flex-direction: column;
26 | align-items: flex-start;
27 | }
28 |
29 | .GameSimulatorPanel {
30 | background-color: white;
31 | width: 800px;
32 | height: 100%;
33 | overflow: scroll;
34 | padding: 20px;
35 | }
36 |
--------------------------------------------------------------------------------
/src/pages/sicp/subcomponents/__tests__/SicpLatex.test.tsx:
--------------------------------------------------------------------------------
1 | import { shallowRender } from 'src/commons/utils/TestUtils';
2 |
3 | import SicpLatex from '../SicpLatex';
4 |
5 | describe('Sicp latex renders', () => {
6 | test('correctly block', () => {
7 | const props = {
8 | math: '1+1',
9 | inline: false
10 | };
11 |
12 | const tree = shallowRender();
13 | expect(tree).toMatchSnapshot();
14 | });
15 |
16 | test('correctly inline', () => {
17 | const props = {
18 | math: '1+1',
19 | inline: true
20 | };
21 |
22 | const tree = shallowRender();
23 | expect(tree).toMatchSnapshot();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/commons/application/__tests__/Application.test.tsx:
--------------------------------------------------------------------------------
1 | import { useTypedSelector } from 'src/commons/utils/Hooks';
2 | import { shallowRender } from 'src/commons/utils/TestUtils';
3 | import { Mock, vi } from 'vitest';
4 |
5 | import Application from '../Application';
6 |
7 | vi.mock('react-redux', async importOriginal => ({
8 | ...(await importOriginal()),
9 | useDispatch: vi.fn(),
10 | useSelector: vi.fn()
11 | }));
12 | const useSelectorMock = useTypedSelector as Mock;
13 |
14 | test('Application renders correctly', () => {
15 | useSelectorMock.mockReturnValue({ name: 'Bob' });
16 |
17 | const app = ;
18 | const tree = shallowRender(app);
19 | expect(tree).toMatchSnapshot();
20 | });
21 |
--------------------------------------------------------------------------------
/src/styles/Login.module.scss:
--------------------------------------------------------------------------------
1 | @import '_global';
2 |
3 | .Login {
4 | background-size: cover;
5 | background-image:
6 | linear-gradient(to right, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.3)),
7 | url('#{$images-path}/login_background.jpg');
8 | background-repeat: no-repeat;
9 | background-attachment: fixed;
10 | height: 100%;
11 | width: 100%;
12 | display: flex;
13 | align-items: center;
14 | justify-content: center;
15 | }
16 |
17 | .login-header {
18 | display: flex;
19 | align-items: center;
20 | justify-content: center;
21 | vertical-align: text-top;
22 |
23 | .login-icon {
24 | margin: 0.1rem 0.5rem 0.5rem 0;
25 | vertical-align: middle;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/public/assets/mockDefaultCheckpoint.txt:
--------------------------------------------------------------------------------
1 | startingLoc: room
2 |
3 | locations
4 | crashsite, /locations/crashSite/normal.png, Crash Site
5 | classroom, /locations/classroom/classOn.png, Class Room
6 | emergency, /locations/classroom/emergency.png, Emergency
7 | hallway, /locations/hallway/normal.png, Hallway
8 | room, /locations/yourRoom-dim/normal.png, Student Room
9 |
10 | room
11 | modes: move
12 | nav: hallway
13 |
14 | hallway
15 | modes: move
16 | nav: room, emergency, classroom
17 |
18 | classroom
19 | modes: move
20 | nav: hallway, crashsite
21 |
22 | crashsite
23 | modes: move
24 | nav: classroom
25 |
26 | emergency
27 | modes: move
28 | nav: hallway
29 |
--------------------------------------------------------------------------------
/src/features/directory/flagDirectoryPluginUrl.ts:
--------------------------------------------------------------------------------
1 | import { put } from 'redux-saga/effects';
2 |
3 | import { createFeatureFlag } from '../../commons/featureFlags';
4 | import { featureSelector } from '../../commons/featureFlags/featureSelector';
5 | import PluginDirectoryActions from './PluginDirectoryActions';
6 |
7 | export const flagDirectoryPluginUrl = createFeatureFlag(
8 | 'directory.plugin.url',
9 | 'https://source-academy.github.io/plugin-directory/directory.json',
10 | 'The URL where the plugin directory may be found.',
11 | function* () {
12 | yield put(PluginDirectoryActions.fetchPlugins());
13 | }
14 | );
15 |
16 | export const selectDirectoryPluginUrl = featureSelector(flagDirectoryPluginUrl);
17 |
--------------------------------------------------------------------------------
/src/pages/academy/grading/subcomponents/GradingFilterable.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classes from 'src/styles/Grading.module.scss';
3 |
4 | type Props = {
5 | value: string;
6 | children?: React.ReactNode;
7 | filterMode: boolean;
8 | };
9 |
10 | const GradingFilterable: React.FC = ({ value, children, filterMode }) => {
11 | return (
12 |
23 | );
24 | };
25 |
26 | export default GradingFilterable;
27 |
--------------------------------------------------------------------------------
/src/features/game/phase/GamePhaseTypes.ts:
--------------------------------------------------------------------------------
1 | export enum GamePhaseType {
2 | Move = 'Move',
3 | Explore = 'Explore',
4 | Talk = 'Talk',
5 | Menu = 'Menu',
6 | EscapeMenu = 'EscapeMenu',
7 | None = 'None',
8 | Sequence = 'Sequence',
9 | Dashboard = 'Dashboard'
10 | }
11 |
12 | /**
13 | * A terminal phase should only ever be the top-most phase in the phase stack.
14 | * In particular, this means the phase stack should only have at most one
15 | * terminal phase on it at any point. A terminal phase should be popped from
16 | * the stack before another phase is pushed on.
17 | */
18 | export enum GameTerminalPhaseType {
19 | EscapeMenu = GamePhaseType.EscapeMenu,
20 | Dashboard = GamePhaseType.Dashboard
21 | }
22 |
--------------------------------------------------------------------------------
/src/commons/achievement/card/AchievementXp.tsx:
--------------------------------------------------------------------------------
1 | import { Icon } from '@blueprintjs/core';
2 | import { IconNames } from '@blueprintjs/icons';
3 | import React from 'react';
4 |
5 | const stringifyXp = (xp: number, isBonus: boolean) => {
6 | return (isBonus ? 'Total ' : '') + xp + ' XP';
7 | };
8 |
9 | type Props = {
10 | isBonus: boolean;
11 | xp: number;
12 | };
13 |
14 | const AchievementXp: React.FC = ({ isBonus, xp }) => {
15 | return (
16 |
17 | {xp !== 0 && (
18 | <>
19 |
20 |
{stringifyXp(xp, isBonus)}
21 | >
22 | )}
23 |
24 | );
25 | };
26 |
27 | export default AchievementXp;
28 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh-SG/commons.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBar": {
3 | "Ground Control": "课程管理",
4 | "Dashboard": "仪表板",
5 | "Sourcereel": "源代码卷轴",
6 | "Team Formation": "团队组建",
7 | "Grading": "评分",
8 | "Game Simulator": "游戏模拟器",
9 | "Admin Panel": "管理面板",
10 | "Stories": "故事",
11 | "Sourcecast": "源代码广播",
12 | "Playground": "游乐场",
13 | "SICP JS": "SICP JS",
14 | "Achievements": "成就",
15 | "Quests": "任务",
16 | "Paths": "路径",
17 | "Contests": "比赛",
18 | "Others": "其他"
19 | },
20 | "dropdown": {
21 | "My Courses": "我的课程",
22 | "Create Course": "创建课程",
23 | "Logout": "登出",
24 | "Settings": "设置",
25 | "About": "关于",
26 | "Help": "帮助"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/pages/notFound/NotFound.tsx:
--------------------------------------------------------------------------------
1 | import { Classes, NonIdealState } from '@blueprintjs/core';
2 | import { IconNames } from '@blueprintjs/icons';
3 | import classNames from 'classnames';
4 | import React from 'react';
5 |
6 | const NotFound: React.FC = () => (
7 |
8 |
13 |
14 | );
15 |
16 | // react-router lazy loading
17 | // https://reactrouter.com/en/main/route/lazy
18 | export const Component = NotFound;
19 | Component.displayName = 'NotFound';
20 |
21 | export default NotFound;
22 |
--------------------------------------------------------------------------------
/src/features/directory/flagDirectoryLanguageUrl.ts:
--------------------------------------------------------------------------------
1 | import { put } from 'redux-saga/effects';
2 |
3 | import { createFeatureFlag } from '../../commons/featureFlags';
4 | import { featureSelector } from '../../commons/featureFlags/featureSelector';
5 | import LanguageDirectoryActions from './LanguageDirectoryActions';
6 |
7 | export const flagDirectoryLanguageUrl = createFeatureFlag(
8 | 'directory.language.url',
9 | 'https://source-academy.github.io/language-directory/directory.json',
10 | 'The URL where the language directory may be found.',
11 | function* () {
12 | yield put(LanguageDirectoryActions.fetchLanguages());
13 | }
14 | );
15 |
16 | export const selectDirectoryLanguageUrl = featureSelector(flagDirectoryLanguageUrl);
17 |
--------------------------------------------------------------------------------
/src/pages/contributors/Contributors.tsx:
--------------------------------------------------------------------------------
1 | import { Card, Elevation } from '@blueprintjs/core';
2 | import React from 'react';
3 |
4 | import ContributorsDetails from './subcomponents/ContributorsDetails';
5 | import ContributorsList from './subcomponents/ContributorsList';
6 |
7 | const Contributors: React.FC = () => (
8 |
9 |
10 |
11 |
12 |
13 |
14 | );
15 |
16 | // react-router lazy loading
17 | // https://reactrouter.com/en/main/route/lazy
18 | export const Component = Contributors;
19 | Component.displayName = 'Contributors';
20 |
21 | export default Contributors;
22 |
--------------------------------------------------------------------------------
/src/features/gameSimulator/GameSimulatorTypes.ts:
--------------------------------------------------------------------------------
1 | export enum GameSimulatorState {
2 | DEFAULT = 'DEFAULT',
3 | ASSETVIEWER = 'ASSETVIEWER',
4 | CHAPTERSIMULATOR = 'CHAPTERSIMULATOR',
5 | CHAPTERPUBLISHER = 'CHAPTERPUBLISHER'
6 | }
7 |
8 | export type ChapterDetail = {
9 | id: string;
10 | openAt: string;
11 | closeAt: string;
12 | title: string;
13 | filenames: string[];
14 | isPublished: boolean;
15 | imageUrl: string;
16 | };
17 |
18 | export type ChapterSimProps = {
19 | chapterDetail: ChapterDetail;
20 | chapterFilenames?: string[];
21 | };
22 |
23 | export type AssetProps = {
24 | assetPath: string;
25 | };
26 |
27 | export type StorageProps = {
28 | storageName: string;
29 | s3TxtFiles: string[];
30 | };
31 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/sideContent/contestLeaderboard.json:
--------------------------------------------------------------------------------
1 | {
2 | "noEntries": "There are no eligible contest leaderboard entries found.",
3 | "titles": {
4 | "score": "Score Leaderboard",
5 | "popularVote": "Popular Vote Leaderboard",
6 | "default": "Contest Leaderboard"
7 | },
8 | "tooltips": {
9 | "score": "View the highest scoring contest entries!",
10 | "popularVote": "View the most popular contest entries!",
11 | "default": "View the top-rated contest entries!"
12 | },
13 | "headers": {
14 | "studentName": "Student Name",
15 | "rank": "Rank",
16 | "score": {
17 | "calculated": "Calculated Score",
18 | "popularity": "Popularity Score",
19 | "default": "Metric"
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/pages/academy/grading/subcomponents/GradingColumnFilters.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { ColumnFilterBadge } from './GradingBadges';
4 |
5 | type Props = {
6 | filters: string[];
7 | onFilterRemove: (toRemove: string) => void;
8 | filtersName: string[];
9 | };
10 |
11 | const GradingColumnFilters: React.FC = ({ filters, onFilterRemove, filtersName }) => {
12 | return (
13 |
14 | {filters.map((filter, index) => (
15 |
21 | ))}
22 |
23 | );
24 | };
25 |
26 | export default GradingColumnFilters;
27 |
--------------------------------------------------------------------------------
/src/styles/GradingCommentSelector.module.scss:
--------------------------------------------------------------------------------
1 | @import '_global';
2 |
3 | .grading-comment-selector {
4 | text-align: center;
5 | display: flex !important;
6 | justify-content: center;
7 | flex-direction: column;
8 | font-size: 0.875rem;
9 | border-radius: 4px;
10 | background-color: $cadet-color-1;
11 | margin-top: 4px;
12 | margin-bottom: 4px;
13 | padding: 8px;
14 | }
15 |
16 | .grading-comment-selector-title {
17 | margin-bottom: 12px;
18 | }
19 |
20 | .grading-comment-selector-item {
21 | margin: 3px;
22 | padding: 7px;
23 | background-color: $cadet-color-2;
24 | border: 1px solid $cadet-color-3;
25 | width: 100%;
26 | cursor: pointer;
27 |
28 | &:hover {
29 | background-color: $cadet-color-3;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/commons/WorkspaceSettingsContext.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export enum EditorBinding {
4 | NONE = '',
5 | VIM = 'vim',
6 | EMACS = 'emacs'
7 | }
8 |
9 | export type WorkspaceSettings = {
10 | editorBinding: EditorBinding;
11 | };
12 |
13 | export const defaultWorkspaceSettings: WorkspaceSettings = {
14 | editorBinding: EditorBinding.NONE
15 | };
16 |
17 | /**
18 | * The WorkspaceSettingsContext stores the local storage-based application settings.
19 | *
20 | * The local storage state is initialized in Application.tsx via the useLocalStorageState hook.
21 | */
22 | export const WorkspaceSettingsContext = React.createContext<
23 | [WorkspaceSettings, React.Dispatch>] | null
24 | >(null);
25 |
--------------------------------------------------------------------------------
/src/commons/application/reducers/VscodeReducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer, Reducer } from '@reduxjs/toolkit';
2 |
3 | import { SourceActionType } from '../../utils/ActionsHelper';
4 | import VscodeActions from '../actions/VscodeActions';
5 | import { defaultVscode } from '../ApplicationTypes';
6 | import { VscodeState } from '../types/VscodeTypes';
7 |
8 | export const VscodeReducer: Reducer = (
9 | state = defaultVscode,
10 | action
11 | ) => {
12 | state = newVscodeReducer(state, action);
13 | return state;
14 | };
15 |
16 | const newVscodeReducer = createReducer(defaultVscode, builder => {
17 | builder.addCase(VscodeActions.setVscode, state => {
18 | return { ...state, ...{ isVscode: true } };
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/src/features/achievement/__tests__/AchievementReducer.test.ts:
--------------------------------------------------------------------------------
1 | import { defaultAchievement } from 'src/commons/application/ApplicationTypes';
2 |
3 | import AchievementActions from '../AchievementActions';
4 | import { AchievementReducer } from '../AchievementReducer';
5 | import { AchievementItem, AchievementState } from '../AchievementTypes';
6 |
7 | test('SAVE_ACHIEVEMENTS works correctly on default achievements', () => {
8 | const achievementItems: AchievementItem[] = [];
9 | const action = {
10 | type: AchievementActions.saveAchievements.type,
11 | payload: achievementItems
12 | } as const;
13 | const result: AchievementState = AchievementReducer(defaultAchievement, action);
14 |
15 | expect(result).toEqual(defaultAchievement);
16 | });
17 |
--------------------------------------------------------------------------------
/src/commons/mobileWorkspace/mobileSideContent/MobileControlBar.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Popover } from '@blueprintjs/core';
2 | import { IconNames } from '@blueprintjs/icons';
3 |
4 | import { ControlBarProps } from '../../controlBar/ControlBar';
5 |
6 | const MobileControlBar: React.FC = props => {
7 | const controlBarMenu = (
8 |
9 | {props.editorButtons}
10 | {props.flowButtons}
11 | {props.editingWorkspaceButtons}
12 |
13 | );
14 |
15 | return (
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default MobileControlBar;
23 |
--------------------------------------------------------------------------------
/src/commons/sagas/__tests__/GitHubPersistenceSaga.test.ts:
--------------------------------------------------------------------------------
1 | import { expectSaga } from 'redux-saga-test-plan';
2 | import SessionActions from 'src/commons/application/actions/SessionActions';
3 | import { vi } from 'vitest';
4 |
5 | import { actions } from '../../utils/ActionsHelper';
6 | import GitHubPersistenceSaga from '../GitHubPersistenceSaga';
7 |
8 | // mock away the store
9 | vi.mock('../../../pages/createStore');
10 |
11 | test('logoutGitHub results in REMOVE_GITHUB_OCTOKIT_OBJECT being dispatched', async () => {
12 | await expectSaga(GitHubPersistenceSaga)
13 | .put({
14 | type: SessionActions.removeGitHubOctokitObjectAndAccessToken.type,
15 | payload: {}
16 | })
17 | .dispatch(actions.logoutGitHub())
18 | .silentRun();
19 | });
20 |
--------------------------------------------------------------------------------
/src/features/game/dashboard/GameDashboardConstants.ts:
--------------------------------------------------------------------------------
1 | import FontAssets from '../assets/FontAssets';
2 | import { screenSize } from '../commons/CommonConstants';
3 | import { BitmapFontStyle } from '../commons/CommonTypes';
4 |
5 | export const pageBannerTextStyle: BitmapFontStyle = {
6 | key: FontAssets.alienLeagueFont.key,
7 | size: 35,
8 | align: Phaser.GameObjects.BitmapText.ALIGN_LEFT
9 | };
10 |
11 | const DashboardConstants = {
12 | backButton: { y: screenSize.y * 0.3 },
13 | page: { yStart: -screenSize.y * 0.3, ySpace: 150 },
14 | pageTextConfig: { x: screenSize.x * 0.3, y: 0, oriX: 0.1, oriY: 0.5 },
15 | pageArea: { x: -869, y: -412, width: screenSize.x * 0.72, height: screenSize.y * 0.77 }
16 | };
17 |
18 | export default DashboardConstants;
19 |
--------------------------------------------------------------------------------
/src/features/game/layer/GameLayerTypes.ts:
--------------------------------------------------------------------------------
1 | export enum Layer {
2 | Effects,
3 | Background,
4 | Character,
5 | Speaker,
6 | PopUp,
7 | Dialogue,
8 | SpeakerBox,
9 | UI,
10 | Objects,
11 | BBox,
12 | Escape,
13 | Selector,
14 | Dashboard,
15 | WorkerMessage,
16 | QuizSpeakerBox,
17 | QuizSpeaker
18 | }
19 |
20 | // Back to Front
21 | export const defaultLayerSequence = [
22 | Layer.Background,
23 | Layer.Selector,
24 | Layer.Objects,
25 | Layer.BBox,
26 | Layer.Character,
27 | Layer.Speaker,
28 | Layer.QuizSpeaker,
29 | Layer.PopUp,
30 | Layer.Dialogue,
31 | Layer.SpeakerBox,
32 | Layer.QuizSpeakerBox,
33 | Layer.Effects,
34 | Layer.Dashboard,
35 | Layer.Escape,
36 | Layer.UI,
37 | Layer.WorkerMessage
38 | ];
39 |
--------------------------------------------------------------------------------
/src/commons/sagas/WorkspaceSaga/helpers/dumpDisplayBuffer.ts:
--------------------------------------------------------------------------------
1 | import { put, StrictEffect } from 'redux-saga/effects';
2 |
3 | import { actions } from '../../../utils/ActionsHelper';
4 | import DisplayBufferService from '../../../utils/DisplayBufferService';
5 | import { WorkspaceLocation } from '../../../workspace/WorkspaceTypes';
6 |
7 | export function* dumpDisplayBuffer(
8 | workspaceLocation: WorkspaceLocation,
9 | isStoriesBlock: boolean = false,
10 | storyEnv?: string
11 | ): Generator {
12 | if (!isStoriesBlock) {
13 | yield put(actions.handleConsoleLog(workspaceLocation, ...DisplayBufferService.dump()));
14 | } else {
15 | yield put(actions.handleStoriesConsoleLog(storyEnv!, ...DisplayBufferService.dump()));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/commons/featureFlags/publicFlags.ts:
--------------------------------------------------------------------------------
1 | import { flagConductorEnable } from '../../features/conductor/flagConductorEnable';
2 | import { flagConductorEvaluatorUrl } from '../../features/conductor/flagConductorEvaluatorUrl';
3 | import { flagDirectoryLanguageEnable } from '../../features/directory/flagDirectoryLanguageEnable';
4 | import { flagDirectoryLanguageUrl } from '../../features/directory/flagDirectoryLanguageUrl';
5 | import { flagDirectoryPluginUrl } from '../../features/directory/flagDirectoryPluginUrl';
6 | import { FeatureFlag } from './FeatureFlag';
7 |
8 | export const publicFlags: FeatureFlag[] = [
9 | flagConductorEnable,
10 | flagConductorEvaluatorUrl,
11 | flagDirectoryLanguageEnable,
12 | flagDirectoryLanguageUrl,
13 | flagDirectoryPluginUrl
14 | ];
15 |
--------------------------------------------------------------------------------
/src/commons/sideContent/content/SideContentCanvasOutput.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 |
3 | type Props = {
4 | canvas: HTMLCanvasElement;
5 | };
6 |
7 | /**
8 | * Takes the output of the rendered graphics (in a hidden canvas tag under )
9 | * and makes it into a new