diff --git a/src/commons/application/types/SessionTypes.ts b/src/commons/application/types/SessionTypes.ts index be4b05e414..8171a48798 100644 --- a/src/commons/application/types/SessionTypes.ts +++ b/src/commons/application/types/SessionTypes.ts @@ -93,6 +93,7 @@ export type SessionState = { readonly sourceChapter?: number; readonly sourceVariant?: Variant; readonly moduleHelpText?: string; + readonly assetsPrefix?: string; readonly assessmentConfigurations?: AssessmentConfiguration[]; readonly userCourseRegistrations?: AdminPanelCourseRegistration[]; @@ -155,6 +156,7 @@ export type CourseConfiguration = { sourceChapter: number; sourceVariant: Variant; moduleHelpText: string; + assetsPrefix: string; }; export type AdminPanelCourseRegistration = { diff --git a/src/commons/mocks/UserMocks.ts b/src/commons/mocks/UserMocks.ts index ae291e402e..caa40d4e76 100644 --- a/src/commons/mocks/UserMocks.ts +++ b/src/commons/mocks/UserMocks.ts @@ -130,7 +130,8 @@ export const mockCourseConfigurations: CourseConfiguration[] = [ enableSourcecast: true, sourceChapter: 1, sourceVariant: 'default', - moduleHelpText: '' + moduleHelpText: '', + assetsPrefix: '' }, { courseName: `CS2040S Data Structures and Algorithms (AY20/21 Sem 2)`, @@ -141,7 +142,8 @@ export const mockCourseConfigurations: CourseConfiguration[] = [ enableSourcecast: false, sourceChapter: 2, sourceVariant: 'default', - moduleHelpText: 'Help Text!' + moduleHelpText: 'Help Text!', + assetsPrefix: '' } ]; diff --git a/src/commons/sagas/__tests__/BackendSaga.ts b/src/commons/sagas/__tests__/BackendSaga.ts index 71e05d08b1..11454ff563 100644 --- a/src/commons/sagas/__tests__/BackendSaga.ts +++ b/src/commons/sagas/__tests__/BackendSaga.ts @@ -165,7 +165,8 @@ const mockCourseConfiguration1: CourseConfiguration = { enableSourcecast: true, sourceChapter: 1, sourceVariant: 'default' as Variant, - moduleHelpText: 'Help text' + moduleHelpText: 'Help text', + assetsPrefix: '' }; const mockCourseRegistration2: CourseRegistration = { @@ -194,7 +195,8 @@ const mockCourseConfiguration2: CourseConfiguration = { enableSourcecast: true, sourceChapter: 4, sourceVariant: 'default' as Variant, - moduleHelpText: 'Help text' + moduleHelpText: 'Help text', + assetsPrefix: '' }; const mockAssessmentConfigurations: AssessmentConfiguration[] = [ @@ -860,7 +862,8 @@ describe('Test UPDATE_COURSE_CONFIG action', () => { enableSourcecast: false, sourceChapter: 4, sourceVariant: 'default', - moduleHelpText: 'Help' + moduleHelpText: 'Help', + assetsPrefix: '' }; test('when course config is changed', () => { diff --git a/src/commons/utils/Constants.ts b/src/commons/utils/Constants.ts index c43a8219e9..802743a2b0 100644 --- a/src/commons/utils/Constants.ts +++ b/src/commons/utils/Constants.ts @@ -79,7 +79,7 @@ export enum Links { resourcesForEducators = 'https://about.sourceacademy.org/educator/README.html', resourcesForLearners = 'https://about.sourceacademy.org/learner/README.html', - sourceAcademyAssets = 'https://source-academy-assets.s3-ap-southeast-1.amazonaws.com', + sourceAcademyAssets = 'https://source-academy-assets.s3-ap-southeast-1.amazonaws.com/', sourceDocs = 'https://docs.sourceacademy.org/', techSVC = 'mailto:techsvc@comp.nus.edu.sg', techSVCNumber = '6516 2736', diff --git a/src/features/game/assets/TextAssets.ts b/src/features/game/assets/TextAssets.ts index af12a45686..7eea3dfa76 100644 --- a/src/features/game/assets/TextAssets.ts +++ b/src/features/game/assets/TextAssets.ts @@ -1,6 +1,6 @@ -import { Constants } from '../commons/CommonConstants'; +import { toS3Path } from '../utils/GameUtils'; -export const toTxtPath = (path: string) => `${Constants.assetsFolder}/stories/${path}`; +export const toTxtPath = (path: string) => toS3Path(`/stories/${path}`, true); const TextAssets = { defaultCheckpoint: { key: 'default-chap', path: toTxtPath('defaultCheckpoint.txt') }, diff --git a/src/features/game/mode/explore/GameModeExploreConstants.ts b/src/features/game/mode/explore/GameModeExploreConstants.ts index 898a1507e0..fcbce00cd4 100644 --- a/src/features/game/mode/explore/GameModeExploreConstants.ts +++ b/src/features/game/mode/explore/GameModeExploreConstants.ts @@ -1,9 +1,9 @@ -import { Constants } from '../../commons/CommonConstants'; +import { toS3Path } from '../../utils/GameUtils'; const ExploreModeConstants = { - normal: `url(${Constants.assetsFolder}/ui/magnifying.png), pointer`, - hover: `url(${Constants.assetsFolder}/ui/magnifying_trigg.png), pointer`, - checked: `url(${Constants.assetsFolder}/ui/magnifying_check.png), pointer` + normal: `url(${toS3Path('/ui/magnifying.png', false)}), pointer`, + hover: `url(${toS3Path('/ui/magnifying_trigg.png', false)}), pointer`, + checked: `url(${toS3Path('/ui/magnifying_check.png', false)}), pointer` }; export default ExploreModeConstants; diff --git a/src/features/game/parser/AwardParser.ts b/src/features/game/parser/AwardParser.ts index 705ece1bb9..23e3c853bc 100644 --- a/src/features/game/parser/AwardParser.ts +++ b/src/features/game/parser/AwardParser.ts @@ -46,7 +46,7 @@ class AwardParser { AwardParser.awardsMapping.set(id, { id, assetKey, - assetPath: toS3Path(assetPath), + assetPath: toS3Path(assetPath, true), title, description, completed: true diff --git a/src/features/game/parser/RoomPreviewParser.ts b/src/features/game/parser/RoomPreviewParser.ts index a13c35bed9..f04cda4553 100644 --- a/src/features/game/parser/RoomPreviewParser.ts +++ b/src/features/game/parser/RoomPreviewParser.ts @@ -22,7 +22,7 @@ class RoomPreviewParser { const assetParagraphs = StringUtils.splitToParagraph(assetLines); assetParagraphs.forEach(([assesmentId, assetPath]: [string, string[]]) => { - RoomPreviewParser.backgroundMapping.set(assesmentId, toS3Path(assetPath[0])); + RoomPreviewParser.backgroundMapping.set(assesmentId, toS3Path(assetPath[0], true)); }); return RoomPreviewParser.backgroundMapping; } diff --git a/src/features/game/scenes/chapterSelect/ChapterSelect.ts b/src/features/game/scenes/chapterSelect/ChapterSelect.ts index 164d36c94b..6fca665128 100644 --- a/src/features/game/scenes/chapterSelect/ChapterSelect.ts +++ b/src/features/game/scenes/chapterSelect/ChapterSelect.ts @@ -79,7 +79,7 @@ class ChapterSelect extends Phaser.Scene { await Promise.all( this.getGameChapters().map( async chapterDetail => - await loadImage(this, chapterDetail.imageUrl, toS3Path(chapterDetail.imageUrl)) + await loadImage(this, chapterDetail.imageUrl, toS3Path(chapterDetail.imageUrl, true)) ) ); } diff --git a/src/features/game/scenes/entry/Entry.ts b/src/features/game/scenes/entry/Entry.ts index c498f3efb1..88f5e2b3e5 100644 --- a/src/features/game/scenes/entry/Entry.ts +++ b/src/features/game/scenes/entry/Entry.ts @@ -52,7 +52,7 @@ class Entry extends Phaser.Scene { * and load all the necessary assets. */ private async preloadAwards() { - const awardsMappingTxt = this.cache.text.get(TextAssets.awardsMapping.key); + const awardsMappingTxt = this.cache.text.get(TextAssets.awardsMapping.key) || ''; const awardsMapping = AwardParser.parse(awardsMappingTxt); SourceAcademyGame.getInstance().setAwardsMapping(awardsMapping); await Promise.all( @@ -67,7 +67,7 @@ class Entry extends Phaser.Scene { * and load all the necessary assets. */ private async preloadRoomPreviewBackgrounds() { - const roomPreviewMappingTxt = this.cache.text.get(TextAssets.roomPreviewMapping.key); + const roomPreviewMappingTxt = this.cache.text.get(TextAssets.roomPreviewMapping.key) || ''; const roomPreviewMapping = RoomPreviewParser.parse(roomPreviewMappingTxt); SourceAcademyGame.getInstance().setRoomPreviewMapping(roomPreviewMapping); await Promise.all( @@ -82,7 +82,9 @@ class Entry extends Phaser.Scene { */ private preloadAssets() { SourceAcademyGame.getInstance().getSoundManager().loadSoundAssetMap(SoundAssets); - Object.values(ImageAssets).forEach(asset => this.load.image(asset.key, toS3Path(asset.path))); + Object.values(ImageAssets).forEach(asset => + this.load.image(asset.key, toS3Path(asset.path, false)) + ); Object.values(FontAssets).forEach(asset => this.load.bitmapFont(asset.key, asset.pngPath, asset.fntPath) ); diff --git a/src/features/game/scenes/gameManager/GameManager.ts b/src/features/game/scenes/gameManager/GameManager.ts index e9a85d70a0..cc541be701 100644 --- a/src/features/game/scenes/gameManager/GameManager.ts +++ b/src/features/game/scenes/gameManager/GameManager.ts @@ -132,10 +132,10 @@ class GameManager extends Phaser.Scene { private loadImage(image: ImageAsset, assetKey: AssetKey) { switch (image.type) { case AssetType.Image: - this.load.image(assetKey, toS3Path(image.path)); + this.load.image(assetKey, toS3Path(image.path, true)); break; case AssetType.Sprite: - this.load.spritesheet(assetKey, toS3Path(image.path), image.config); + this.load.spritesheet(assetKey, toS3Path(image.path, true), image.config); break; default: break; diff --git a/src/features/game/scenes/roomPreview/RoomPreview.ts b/src/features/game/scenes/roomPreview/RoomPreview.ts index 1ee6107b73..2e800d2178 100644 --- a/src/features/game/scenes/roomPreview/RoomPreview.ts +++ b/src/features/game/scenes/roomPreview/RoomPreview.ts @@ -17,7 +17,7 @@ import GamePhaseManager from '../../phase/GamePhaseManager'; import { GamePhaseType } from '../../phase/GamePhaseTypes'; import SourceAcademyGame from '../../SourceAcademyGame'; import { createButton } from '../../utils/ButtonUtils'; -import { mandatory } from '../../utils/GameUtils'; +import { mandatory, toS3Path } from '../../utils/GameUtils'; import { loadImage, loadSound, loadSpritesheet } from '../../utils/LoaderUtils'; import { resizeOverflow } from '../../utils/SpriteUtils'; import { RoomConstants, roomDefaultCode } from './RoomPreviewConstants'; @@ -173,7 +173,7 @@ export default class RoomPreview extends Phaser.Scene { preloadImageMap: this.preloadImageMap, preloadSoundMap: this.preloadSoundMap, preloadSpritesheetMap: this.preloadSpritesheetMap, - remotePath: Constants.assetsFolder, + remotePath: (file: string) => toS3Path(file, true), screenSize: screenSize, createAward: (x: number, y: number, key: ItemId) => this.createAward(x, y, key) } diff --git a/src/features/game/sound/GameSoundManager.ts b/src/features/game/sound/GameSoundManager.ts index 894aa81b69..8ae17c78d8 100644 --- a/src/features/game/sound/GameSoundManager.ts +++ b/src/features/game/sound/GameSoundManager.ts @@ -75,7 +75,7 @@ class GameSoundManager { public loadSounds(soundAssets: SoundAsset[]) { soundAssets.forEach(asset => { this.addSoundAsset(asset); - this.loadSound(asset.key, toS3Path(asset.path)); + this.loadSound(asset.key, toS3Path(asset.path, true)); }); } @@ -87,7 +87,7 @@ class GameSoundManager { public loadSoundAssetMap(assetMap: AssetMap) { Object.values(assetMap).forEach(asset => { this.addSoundAsset(asset); - this.loadSound(asset.key, toS3Path(asset.path)); + this.loadSound(asset.key, toS3Path(asset.path, false)); }); } diff --git a/src/features/game/utils/GameUtils.ts b/src/features/game/utils/GameUtils.ts index e7e5d246bb..3c52f18386 100644 --- a/src/features/game/utils/GameUtils.ts +++ b/src/features/game/utils/GameUtils.ts @@ -1,3 +1,5 @@ +import { assetsPrefix } from 'src/features/storySimulator/StorySimulatorRequest'; + import { Constants } from '../commons/CommonConstants'; /** @@ -51,10 +53,14 @@ export function limitNumber(value: number, min: number, max: number) { * Appends the s3 file path to a short path name * * @param filename the short path of a filename + * @param courseCoded true iff file is course-specific * @returns {string} new path to file including full s3 link */ -export function toS3Path(fileName: string) { - return Constants.assetsFolder + fileName; +export function toS3Path(fileName: string, courseCoded = false) { + if (fileName.startsWith('/')) { + fileName = fileName.substr(1); + } + return Constants.assetsFolder + (courseCoded ? assetsPrefix() + fileName : fileName); } /** diff --git a/src/features/storySimulator/StorySimulatorRequest.ts b/src/features/storySimulator/StorySimulatorRequest.ts index 8a8c34f259..e83b6aebee 100644 --- a/src/features/storySimulator/StorySimulatorRequest.ts +++ b/src/features/storySimulator/StorySimulatorRequest.ts @@ -32,7 +32,8 @@ const sendRequest = } }; -const courseId = () => store.getState().session.courseId; +export const courseId = () => store.getState().session.courseId; +export const assetsPrefix = () => store.getState().session.assetsPrefix || ''; export const sendAssetRequest = sendRequest(`admin/assets`); export const sendStoryRequest = sendRequest(`stories`); diff --git a/src/features/storySimulator/background/SSBackgroundManager.ts b/src/features/storySimulator/background/SSBackgroundManager.ts index ab8a8e2135..d977d6bd49 100644 --- a/src/features/storySimulator/background/SSBackgroundManager.ts +++ b/src/features/storySimulator/background/SSBackgroundManager.ts @@ -1,7 +1,7 @@ -import { Constants, screenCenter } from 'src/features/game/commons/CommonConstants'; +import { screenCenter } from 'src/features/game/commons/CommonConstants'; import { AssetKey } from 'src/features/game/commons/CommonTypes'; import { Layer } from 'src/features/game/layer/GameLayerTypes'; -import { mandatory } from 'src/features/game/utils/GameUtils'; +import { mandatory, toS3Path } from 'src/features/game/utils/GameUtils'; import { loadImage } from '../../game/utils/LoaderUtils'; import ObjectPlacement from '../scenes/ObjectPlacement/ObjectPlacement'; @@ -44,7 +44,7 @@ export default class SSBackgroundManager { const assetKeyOnLoad = await loadImage( this.getObjectPlacement(), backgroundAssetKey, - Constants.assetsFolder + shortPath + toS3Path(shortPath, true) ); this.renderBackground(assetKeyOnLoad); } diff --git a/src/features/storySimulator/objects/SSObjectManager.ts b/src/features/storySimulator/objects/SSObjectManager.ts index 08245aa5d1..c279b8b288 100644 --- a/src/features/storySimulator/objects/SSObjectManager.ts +++ b/src/features/storySimulator/objects/SSObjectManager.ts @@ -1,7 +1,7 @@ -import { Constants, screenCenter } from 'src/features/game/commons/CommonConstants'; +import { screenCenter } from 'src/features/game/commons/CommonConstants'; import { AssetKey, ItemId } from 'src/features/game/commons/CommonTypes'; import { Layer } from 'src/features/game/layer/GameLayerTypes'; -import { mandatory } from 'src/features/game/utils/GameUtils'; +import { mandatory, toS3Path } from 'src/features/game/utils/GameUtils'; import StringUtils from 'src/features/game/utils/StringUtils'; import { loadImage } from '../../game/utils/LoaderUtils'; @@ -46,7 +46,7 @@ export default class SSObjectManager implements ICheckpointLoggable { const assetKeyOnLoad = await loadImage( this.getObjectPlacement(), objectAssetKey, - Constants.assetsFolder + shortPath + toS3Path(shortPath, true) ); this.renderObject(assetKeyOnLoad); } diff --git a/src/features/storySimulator/scenes/mainMenu/MainMenu.ts b/src/features/storySimulator/scenes/mainMenu/MainMenu.ts index 58c69509df..6f4996975b 100644 --- a/src/features/storySimulator/scenes/mainMenu/MainMenu.ts +++ b/src/features/storySimulator/scenes/mainMenu/MainMenu.ts @@ -33,8 +33,12 @@ class MainMenu extends Phaser.Scene { this.layerManager = new GameLayerManager(this); addLoadingScreen(this); - Object.values(ImageAssets).forEach(asset => this.load.image(asset.key, toS3Path(asset.path))); - Object.values(SSImageAssets).forEach(asset => this.load.image(asset.key, toS3Path(asset.path))); + Object.values(ImageAssets).forEach(asset => + this.load.image(asset.key, toS3Path(asset.path, false)) + ); + Object.values(SSImageAssets).forEach(asset => + this.load.image(asset.key, toS3Path(asset.path, false)) + ); Object.values(FontAssets).forEach(asset => this.load.bitmapFont(asset.key, asset.pngPath, asset.fntPath) ); diff --git a/src/pages/__tests__/localStorage.test.ts b/src/pages/__tests__/localStorage.test.ts index bac77d7396..a3588b9efb 100644 --- a/src/pages/__tests__/localStorage.test.ts +++ b/src/pages/__tests__/localStorage.test.ts @@ -20,6 +20,7 @@ const mockShortDefaultState: SavedState = { enableAchievements: defaultState.session.enableAchievements, enableSourcecast: defaultState.session.enableSourcecast, moduleHelpText: defaultState.session.moduleHelpText, + assetsPrefix: defaultState.session.assetsPrefix, assessmentConfigurations: defaultState.session.assessmentConfigurations }, achievements: defaultState.achievement.achievements, diff --git a/src/pages/academy/storySimulator/subcomponents/StorySimulatorAssetViewer.tsx b/src/pages/academy/storySimulator/subcomponents/StorySimulatorAssetViewer.tsx index d83bc27dfb..d3577ca267 100644 --- a/src/pages/academy/storySimulator/subcomponents/StorySimulatorAssetViewer.tsx +++ b/src/pages/academy/storySimulator/subcomponents/StorySimulatorAssetViewer.tsx @@ -1,5 +1,6 @@ import { memo } from 'react'; import { Constants } from 'src/features/game/commons/CommonConstants'; +import { toS3Path } from 'src/features/game/utils/GameUtils'; type AssetProps = { assetPath: string; @@ -18,11 +19,11 @@ const AssetViewer = memo(({ assetPath }: AssetProps) => { asset { (e.target as any).onerror = null; - (e.target as any).src = Constants.assetsFolder + Constants.defaultAssetPath; + (e.target as any).src = toS3Path(Constants.defaultAssetPath, false); }} > diff --git a/src/pages/academy/storySimulator/subcomponents/StorySimulatorChapterEditor.tsx b/src/pages/academy/storySimulator/subcomponents/StorySimulatorChapterEditor.tsx index 398d364be4..b386ba03d5 100644 --- a/src/pages/academy/storySimulator/subcomponents/StorySimulatorChapterEditor.tsx +++ b/src/pages/academy/storySimulator/subcomponents/StorySimulatorChapterEditor.tsx @@ -137,7 +137,7 @@ const ChapterEditor = React.memo(({ chapterDetail, checkpointFilenames }: Chapte />

Image url: - +

Checkpoint Txt Files diff --git a/src/pages/academy/storySimulator/subcomponents/StorySimulatorCheckpointTxtLoader.tsx b/src/pages/academy/storySimulator/subcomponents/StorySimulatorCheckpointTxtLoader.tsx index 119bd96b4b..c39fa720b3 100644 --- a/src/pages/academy/storySimulator/subcomponents/StorySimulatorCheckpointTxtLoader.tsx +++ b/src/pages/academy/storySimulator/subcomponents/StorySimulatorCheckpointTxtLoader.tsx @@ -3,7 +3,7 @@ import 'ace-builds/webpack-resolver'; import { Button, Tab, Tabs } from '@blueprintjs/core'; import React from 'react'; import { toTxtPath } from 'src/features/game/assets/TextAssets'; -import { Constants } from 'src/features/game/commons/CommonConstants'; +import { toS3Path } from 'src/features/game/utils/GameUtils'; type Props = { storageName: string; @@ -30,7 +30,7 @@ function CheckpointTxtLoader({ storageName, s3TxtFiles }: Props) { async function changeChosenFilename(e: any) { const filename = e.target.value; setChosenFilename(filename); - const response = await fetch(`${Constants.assetsFolder}/stories/${filename}`, { + const response = await fetch(toS3Path(`/stories/${filename}`, true), { headers: createHeadersWithCors() }); const txt = await response.text(); diff --git a/src/pages/localStorage.ts b/src/pages/localStorage.ts index 4da411fbc1..6dae91d80d 100644 --- a/src/pages/localStorage.ts +++ b/src/pages/localStorage.ts @@ -53,6 +53,7 @@ export const saveState = (state: OverallState) => { enableAchievements: state.session.enableAchievements, enableSourcecast: state.session.enableSourcecast, moduleHelpText: state.session.moduleHelpText, + assetsPrefix: state.session.assetsPrefix, assessmentConfigurations: state.session.assessmentConfigurations, githubAccessToken: state.session.githubAccessToken },