diff --git a/src/features/game/SourceAcademyGame.ts b/src/features/game/SourceAcademyGame.ts index c711ed3cf6..6db6f918df 100644 --- a/src/features/game/SourceAcademyGame.ts +++ b/src/features/game/SourceAcademyGame.ts @@ -13,7 +13,7 @@ import RoomPreview from 'src/features/game/scenes/roomPreview/RoomPreview'; import Settings from 'src/features/game/scenes/settings/Settings'; import GameSoundManager from 'src/features/game/sound/GameSoundManager'; import { mandatory } from 'src/features/game/utils/GameUtils'; -import { GameSimState } from 'src/features/gameSimulator/GameSimulatorTypes'; +import { GameSimulatorState } from 'src/features/gameSimulator/GameSimulatorTypes'; import { AchievementGoal, AchievementItem } from '../achievement/AchievementTypes'; import { fetchGameChapters } from './chapter/GameChapterHelpers'; @@ -123,7 +123,7 @@ export default class SourceAcademyGame extends Phaser.Game { this.global.roomCode = await getRoomPreviewCode(); } - public setGameSimState(state: GameSimState) { + public setGameSimState(state: GameSimulatorState) { this.global.setGameSimState(state); } diff --git a/src/features/gameSimulator/GameSimulatorConstants.ts b/src/features/gameSimulator/GameSimulatorConstants.ts new file mode 100644 index 0000000000..bc9b3a2f1f --- /dev/null +++ b/src/features/gameSimulator/GameSimulatorConstants.ts @@ -0,0 +1,72 @@ +import * as Phaser from 'phaser'; +import { AssetMap, AssetType, ImageAsset } from 'src/features/game/assets/AssetsTypes'; +import FontAssets from 'src/features/game/assets/FontAssets'; +import { screenSize } from 'src/features/game/commons/CommonConstants'; +import { BitmapFontStyle } from 'src/features/game/commons/CommonTypes'; + +import { dateOneYearFromNow } from './GameSimulatorUtils'; + +export const gameSimulatorConfig = { + debug: true, + type: Phaser.CANVAS, + width: screenSize.x, + height: screenSize.y, + physics: { + default: 'arcade' + }, + scale: { + mode: Phaser.Scale.FIT, + parent: 'game-display' + }, + fps: { + target: 24 + } +}; + +export const gameSimulatorMenuAssets: AssetMap = { + gameSimBg: { + type: AssetType.Image, + key: 'student-room', + path: '/locations/deathCube_ext/shields-down.png' + }, + shortButton: { type: AssetType.Image, key: 'short-button', path: '/ui/shortButton.png' }, + invertedButton: { + type: AssetType.Image, + key: 'inverted-button', + path: '/ui/invertedColorButton.png' + }, + blueUnderlay: { type: AssetType.Image, key: 'blue-underlay', path: '/ui/blueUnderlay.png' }, + topButton: { type: AssetType.Image, key: 'top-button', path: '/ui/topButton.png' }, + colorIcon: { type: AssetType.Image, key: 'color-icon', path: '/ui/colorIcon.png' }, + imageIcon: { type: AssetType.Image, key: 'image-icon', path: '/ui/imageIcon.png' }, + bboxIcon: { type: AssetType.Image, key: 'bbox-icon', path: '/ui/bboxIcon.png' }, + handIcon: { type: AssetType.Image, key: 'hand-icon', path: '/ui/handIcon.png' }, + listIcon: { type: AssetType.Image, key: 'list-icon', path: '/ui/listIcon.png' }, + eraseIcon: { type: AssetType.Image, key: 'erase-icon', path: '/ui/eraserIcon.png' }, + iconBg: { type: AssetType.Image, key: 'icon-bg', path: '/ui/modeIconBg.png' } +}; + +export const gameSimulatorMenuOptStyle: BitmapFontStyle = { + key: FontAssets.zektonDarkFont.key, + size: 35, + align: Phaser.GameObjects.BitmapText.ALIGN_CENTER +}; + +export const gameSimulatorMenuConstants = { + maxOptButtonsRow: 2, + optButton: { xSpace: screenSize.x * 0.9, ySpace: screenSize.y * 0.5 }, + gameTxtStorageName: { + defaultChapter: 'defaultChapter', + checkpointTxt: 'checkpointTxt' + } +}; + +export const defaultChapter = { + id: -1, + title: '', + imageUrl: '/locations/spaceshipBackground.png', + openAt: new Date().toISOString(), + closeAt: dateOneYearFromNow(new Date()).toISOString(), + isPublished: false, + filenames: [] +}; diff --git a/src/features/gameSimulator/scenes/MainMenu.ts b/src/features/gameSimulator/GameSimulatorMenu.ts similarity index 78% rename from src/features/gameSimulator/scenes/MainMenu.ts rename to src/features/gameSimulator/GameSimulatorMenu.ts index f36e4d1122..691885941f 100644 --- a/src/features/gameSimulator/scenes/MainMenu.ts +++ b/src/features/gameSimulator/GameSimulatorMenu.ts @@ -11,9 +11,12 @@ import { createButton } from 'src/features/game/utils/ButtonUtils'; import { mandatory, toS3Path } from 'src/features/game/utils/GameUtils'; import { calcTableFormatPos } from 'src/features/game/utils/StyleUtils'; -import SSImageAssets from '../assets/ImageAssets'; -import { GameSimState } from '../GameSimulatorTypes'; -import mainMenuConstants, { mainMenuOptStyle } from './MainMenuConstants'; +import { + gameSimulatorMenuAssets, + gameSimulatorMenuConstants, + gameSimulatorMenuOptStyle +} from './GameSimulatorConstants'; +import { GameSimulatorState } from './GameSimulatorTypes'; /** * Entry point for Game simulator. @@ -21,7 +24,7 @@ import mainMenuConstants, { mainMenuOptStyle } from './MainMenuConstants'; * User can access different Game simulator * functionalities from here. */ -class MainMenu extends Phaser.Scene { +class GameSimulatorMenu extends Phaser.Scene { private layerManager?: GameLayerManager; constructor() { @@ -36,7 +39,7 @@ class MainMenu extends Phaser.Scene { Object.values(ImageAssets).forEach(asset => this.load.image(asset.key, toS3Path(asset.path, false)) ); - Object.values(SSImageAssets).forEach(asset => + Object.values(gameSimulatorMenuAssets).forEach(asset => this.load.image(asset.key, toS3Path(asset.path, false)) ); Object.values(FontAssets).forEach(asset => @@ -60,9 +63,9 @@ class MainMenu extends Phaser.Scene { const buttonPositions = calcTableFormatPos({ numOfItems: buttons.length, - maxXSpace: mainMenuConstants.optButton.xSpace, - maxYSpace: mainMenuConstants.optButton.ySpace, - numItemLimit: mainMenuConstants.maxOptButtonsRow, + maxXSpace: gameSimulatorMenuConstants.optButton.xSpace, + maxYSpace: gameSimulatorMenuConstants.optButton.ySpace, + numItemLimit: gameSimulatorMenuConstants.maxOptButtonsRow, redistributeLast: true }); @@ -82,21 +85,21 @@ class MainMenu extends Phaser.Scene { private getOptionButtons() { return [ { - text: 'Checkpoint Simulator', + text: 'Simulate Chapters', callback: () => { - SourceAcademyGame.getInstance().setGameSimState(GameSimState.CheckpointSim); + SourceAcademyGame.getInstance().setGameSimState(GameSimulatorState.CHAPTERSIMULATOR); } }, { - text: 'Asset Uploader', + text: 'Publish / Edit Chapters', callback: () => { - SourceAcademyGame.getInstance().setGameSimState(GameSimState.AssetUploader); + SourceAcademyGame.getInstance().setGameSimState(GameSimulatorState.CHAPTERPUBLISHER); } }, { - text: 'Chapter Simulator', + text: 'View / Upload Assets', callback: () => { - SourceAcademyGame.getInstance().setGameSimState(GameSimState.ChapterSim); + SourceAcademyGame.getInstance().setGameSimState(GameSimulatorState.ASSETVIEWER); } } ]; @@ -104,19 +107,19 @@ class MainMenu extends Phaser.Scene { private createOptButton(text: string, xPos: number, yPos: number, callback: any) { return createButton(this, { - assetKey: SSImageAssets.invertedButton.key, + assetKey: gameSimulatorMenuAssets.invertedButton.key, message: text, textConfig: { x: 0, y: 0, oriX: 0.5, oriY: 0.5 }, - bitMapTextStyle: mainMenuOptStyle, + bitMapTextStyle: gameSimulatorMenuOptStyle, onUp: callback }).setPosition(xPos, yPos); } public simulateCheckpoint() { const defaultChapterText = - sessionStorage.getItem(mainMenuConstants.gameTxtStorageName.defaultChapter) || ''; + sessionStorage.getItem(gameSimulatorMenuConstants.gameTxtStorageName.defaultChapter) || ''; const checkpointTxt = - sessionStorage.getItem(mainMenuConstants.gameTxtStorageName.checkpointTxt) || ''; + sessionStorage.getItem(gameSimulatorMenuConstants.gameTxtStorageName.checkpointTxt) || ''; if (defaultChapterText === '' && checkpointTxt === '') { return; } @@ -141,14 +144,14 @@ class MainMenu extends Phaser.Scene { this, screenCenter.x, screenCenter.y, - SSImageAssets.gameSimBg.key + gameSimulatorMenuAssets.gameSimBg.key ); backgroundImg.setDisplaySize(screenSize.x, screenSize.y); const backgroundUnderlay = new Phaser.GameObjects.Image( this, screenCenter.x, screenCenter.y, - SSImageAssets.blueUnderlay.key + gameSimulatorMenuAssets.blueUnderlay.key ).setAlpha(0.5); this.getLayerManager().addToLayer(Layer.Background, backgroundImg); this.getLayerManager().addToLayer(Layer.Background, backgroundUnderlay); @@ -156,4 +159,4 @@ class MainMenu extends Phaser.Scene { public getLayerManager = () => mandatory(this.layerManager); } -export default MainMenu; +export default GameSimulatorMenu; diff --git a/src/features/gameSimulator/GameSimulatorTypes.ts b/src/features/gameSimulator/GameSimulatorTypes.ts index 353e6a69da..ab4b79ab11 100644 --- a/src/features/gameSimulator/GameSimulatorTypes.ts +++ b/src/features/gameSimulator/GameSimulatorTypes.ts @@ -1,8 +1,8 @@ -export enum GameSimState { - Default = 'Default', - AssetUploader = 'AssetUploader', - CheckpointSim = 'CheckpointSim', - ChapterSim = 'ChapterSim' +export enum GameSimulatorState { + DEFAULT = 'DEFAULT', + ASSETVIEWER = 'ASSETVIEWER', + CHAPTERSIMULATOR = 'CHAPTERSIMULATOR', + CHAPTERPUBLISHER = 'CHAPTERPUBLISHER' } export type ChapterDetail = { @@ -14,3 +14,17 @@ export type ChapterDetail = { isPublished: boolean; imageUrl: string; }; + +export type ChapterSimProps = { + chapterDetail: ChapterDetail; + chapterFilenames?: string[]; +}; + +export type AssetProps = { + assetPath: string; +}; + +export type StorageProps = { + storageName: string; + s3TxtFiles: string[]; +}; diff --git a/src/features/gameSimulator/GameSimulatorUtils.ts b/src/features/gameSimulator/GameSimulatorUtils.ts new file mode 100644 index 0000000000..a8b3a4cf41 --- /dev/null +++ b/src/features/gameSimulator/GameSimulatorUtils.ts @@ -0,0 +1,21 @@ +export const createHeadersWithCors = (): Headers => { + const headers = new Headers(); + headers.append('Access-Control-Allow-Origin', '*'); + return headers; +}; + +export const loadFileLocally = (storageName: string, txtFile: File) => { + const reader = new FileReader(); + reader.readAsText(txtFile); + reader.onloadend = _ => { + if (!reader.result) { + return; + } + sessionStorage.setItem(storageName, reader.result.toString()); + }; +}; + +export const dateOneYearFromNow = (date: Date) => { + date.setFullYear(date.getFullYear() + 1); + return date; +}; diff --git a/src/features/gameSimulator/assets/ImageAssets.ts b/src/features/gameSimulator/assets/ImageAssets.ts deleted file mode 100644 index fcadb05256..0000000000 --- a/src/features/gameSimulator/assets/ImageAssets.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { AssetMap, AssetType, ImageAsset } from 'src/features/game/assets/AssetsTypes'; - -const SSImageAssets: AssetMap = { - gameSimBg: { - type: AssetType.Image, - key: 'student-room', - path: '/locations/deathCube_ext/shields-down.png' - }, - shortButton: { type: AssetType.Image, key: 'short-button', path: '/ui/shortButton.png' }, - invertedButton: { - type: AssetType.Image, - key: 'inverted-button', - path: '/ui/invertedColorButton.png' - }, - blueUnderlay: { type: AssetType.Image, key: 'blue-underlay', path: '/ui/blueUnderlay.png' }, - topButton: { type: AssetType.Image, key: 'top-button', path: '/ui/topButton.png' }, - colorIcon: { type: AssetType.Image, key: 'color-icon', path: '/ui/colorIcon.png' }, - imageIcon: { type: AssetType.Image, key: 'image-icon', path: '/ui/imageIcon.png' }, - bboxIcon: { type: AssetType.Image, key: 'bbox-icon', path: '/ui/bboxIcon.png' }, - handIcon: { type: AssetType.Image, key: 'hand-icon', path: '/ui/handIcon.png' }, - listIcon: { type: AssetType.Image, key: 'list-icon', path: '/ui/listIcon.png' }, - eraseIcon: { type: AssetType.Image, key: 'erase-icon', path: '/ui/eraserIcon.png' }, - iconBg: { type: AssetType.Image, key: 'icon-bg', path: '/ui/modeIconBg.png' } -}; - -export default SSImageAssets; diff --git a/src/features/gameSimulator/scenes/MainMenuConstants.ts b/src/features/gameSimulator/scenes/MainMenuConstants.ts deleted file mode 100644 index 5d0bb4de14..0000000000 --- a/src/features/gameSimulator/scenes/MainMenuConstants.ts +++ /dev/null @@ -1,20 +0,0 @@ -import FontAssets from 'src/features/game/assets/FontAssets'; -import { screenSize } from 'src/features/game/commons/CommonConstants'; -import { BitmapFontStyle } from 'src/features/game/commons/CommonTypes'; - -export const mainMenuOptStyle: BitmapFontStyle = { - key: FontAssets.zektonDarkFont.key, - size: 35, - align: Phaser.GameObjects.BitmapText.ALIGN_CENTER -}; - -const SSMainMenuConstants = { - maxOptButtonsRow: 2, - optButton: { xSpace: screenSize.x * 0.9, ySpace: screenSize.y * 0.5 }, - gameTxtStorageName: { - defaultChapter: 'defaultChapter', - checkpointTxt: 'checkpointTxt' - } -}; - -export default SSMainMenuConstants; diff --git a/src/pages/academy/gameSimulator/GameSimulator.tsx b/src/pages/academy/gameSimulator/GameSimulator.tsx index 6fad4b14c3..0c34a90e89 100644 --- a/src/pages/academy/gameSimulator/GameSimulator.tsx +++ b/src/pages/academy/gameSimulator/GameSimulator.tsx @@ -1,32 +1,44 @@ import React from 'react'; import { useTypedSelector } from 'src/commons/utils/Hooks'; -import SourceAcademyGame, { AccountInfo } from 'src/features/game/SourceAcademyGame'; -import { GameSimState } from 'src/features/gameSimulator/GameSimulatorTypes'; +import CheckpointTransition from 'src/features/game/scenes/checkpointTransition/CheckpointTransition'; +import GameManager from 'src/features/game/scenes/gameManager/GameManager'; +import SourceAcademyGame, { AccountInfo, GameType } from 'src/features/game/SourceAcademyGame'; +import { gameSimulatorConfig } from 'src/features/gameSimulator/GameSimulatorConstants'; +import GameSimulatorMenu from 'src/features/gameSimulator/GameSimulatorMenu'; +import { GameSimulatorState } from 'src/features/gameSimulator/GameSimulatorTypes'; -import GameSimulatorAssetFileUploader from './subcomponents/GameSimulatorAssetFileUploader'; -import GameSimulatorAssetSelection from './subcomponents/GameSimulatorAssetSelection'; -import GameSimulatorChapterSim from './subcomponents/GameSimulatorChapterSim'; -import GameSimulatorCheckpointSim from './subcomponents/GameSimulatorCheckpointSim'; -import { createGameSimulatorGame } from './subcomponents/GameSimulatorGame'; +import AssetViewer from './subcomponents/assetViewer/AssetViewer'; +import ChapterPublisher from './subcomponents/chapterPublisher/ChapterPublisher'; +import ChapterSimulator from './subcomponents/chapterSimulator/ChapterSimulator'; /** - * Game simulator main page + * This component renders the Main Page of the Game Simulator. * - * Displays the following elements: + * It displays the following elements: * (1) Game Simulator phaser canvas * (2) Game Simulator control panel * * Game Simulator control panel's content can be altered using - * `setGameSimState` function. This function is passed into story - * simulator phaser game, so that the GameSimulatorMainMenu buttons + * `setGameSimulatorState` function. This function is passed into story + * simulator phaser game, so that the GameSimulatorMenu buttons * are able to control what is shown on the Game Simulator panel. */ -function GameSimulator() { +const GameSimulator: React.FC = () => { const session = useTypedSelector(state => state.session); - const [gameSimState, setGameSimState] = React.useState(GameSimState.Default); + const [gameSimulatorState, setGameSimulatorState] = React.useState( + GameSimulatorState.DEFAULT + ); + + const createGameSimulatorGame = () => { + const game = new SourceAcademyGame(gameSimulatorConfig, GameType.Simulator); + game.scene.add('GameSimulatorMenu', GameSimulatorMenu, true); + game.scene.add('GameManager', GameManager); + game.scene.add('CheckpointTransition', CheckpointTransition); + return game; + }; React.useEffect(() => { - createGameSimulatorGame().setGameSimStateSetter(setGameSimState); + createGameSimulatorGame().setGameSimStateSetter(setGameSimulatorState); }, []); React.useEffect(() => { @@ -42,20 +54,13 @@ function GameSimulator() {
- {gameSimState === GameSimState.Default &&

Welcome to Game simulator!

} - {gameSimState === GameSimState.CheckpointSim && } - {gameSimState === GameSimState.AssetUploader && ( - <> -

Asset uploader

- -

Asset Viewer

- - - )} - {gameSimState === GameSimState.ChapterSim && } + {gameSimulatorState === GameSimulatorState.DEFAULT &&

Welcome to Game simulator!

} + {gameSimulatorState === GameSimulatorState.CHAPTERSIMULATOR && } + {gameSimulatorState === GameSimulatorState.CHAPTERPUBLISHER && } + {gameSimulatorState === GameSimulatorState.ASSETVIEWER && }
); -} +}; export default GameSimulator; diff --git a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorAssetSelection.tsx b/src/pages/academy/gameSimulator/subcomponents/GameSimulatorAssetSelection.tsx deleted file mode 100644 index 7a001ec3d1..0000000000 --- a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorAssetSelection.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Icon, Tree, TreeNodeInfo } from '@blueprintjs/core'; -import { Tooltip2 } from '@blueprintjs/popover2'; -import React from 'react'; -import { useRequest } from 'src/commons/utils/Hooks'; -import { - deleteS3File, - fetchAssetPaths, - s3AssetFolders -} from 'src/features/gameSimulator/GameSimulatorService'; - -import { assetPathsToTree, treeMap } from './GameSimulatorAssetSelectionHelper'; -import GameSimulatorAssetViewer from './GameSimulatorAssetViewer'; - -type TreeState = { - nodes: TreeNodeInfo[]; -}; - -/** - * This component provides a preview of all the S3 asset files. - * - * When a image is selected, the filename of the image is stored in session storage, - * so Game Simulator's Object Placement can read the filename and load the image. - */ -const GameSimulatorAssetSelection = () => { - const { value: assetPaths } = useRequest(fetchAssetPaths, []); - - const [currentAsset, setCurrentAsset] = React.useState(''); - const [assetTree, setAssetTree] = React.useState({ nodes: [] }); - - React.useEffect(() => { - setAssetTree({ nodes: assetPathsToTree(assetPaths, toolIcons, s3AssetFolders) }); - }, [assetPaths]); - - const handleNodeClick = (nodeData: TreeNodeInfo) => { - treeMap(assetTree.nodes, (node: TreeNodeInfo) => (node.isSelected = false)); - nodeData.isSelected = !nodeData.isSelected; - nodeData.isExpanded = !nodeData.isExpanded; - const selectedPath = nodeData.id.toString(); - if (!nodeData.childNodes) { - setCurrentAsset(selectedPath); - sessionStorage.setItem('selectedAsset', selectedPath); - } - setAssetTree({ ...assetTree }); - }; - - return ( - <> - - - - ); -}; - -/** - * Tools that are added to asset selection, includes: trash-can delete tool - * - * @param filePath the path to asset you want to supply tools for - * @returns {JSX.Element} A trash can that deletes the file given the asset path - */ -const toolIcons = (filePath: string) => ( - - - -); - -/** - * This function deletes an S3 file given the short filepath - * - * @param filePath - the file path e.g. "stories/chapter0.txt" - */ -const deleteFile = (filePath: string) => async () => { - const confirm = window.confirm( - `Are you sure you want to delete ${filePath}?\nThere is no undoing this action!` - ); - alert(confirm ? await deleteS3File(filePath) : 'Whew'); -}; - -export default GameSimulatorAssetSelection; diff --git a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorChapterSim.tsx b/src/pages/academy/gameSimulator/subcomponents/GameSimulatorChapterSim.tsx deleted file mode 100644 index a744496f95..0000000000 --- a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorChapterSim.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import { useRequest } from 'src/commons/utils/Hooks'; -import { fetchChapters, fetchTextAssets } from 'src/features/gameSimulator/GameSimulatorService'; -import { ChapterDetail } from 'src/features/gameSimulator/GameSimulatorTypes'; - -import GameSimulatorChapterEditor from './GameSimulatorChapterEditor'; - -export const inAYear = (date: Date) => { - date.setFullYear(date.getFullYear() + 1); - return date; -}; - -export const createChapterIndex = -1; -const defaultChapter = { - id: createChapterIndex, - title: 'title goes here', - imageUrl: '/locations/telebay/emergency.png', - openAt: new Date().toISOString(), - closeAt: inAYear(new Date()).toISOString(), - isPublished: false, - filenames: [] -}; - -/** - * This components renders the chapter editor/chapter creator component - * based on the chapter chosen in the dropdown. - * - * @param textAssets - the list of all text assets on S3 to choose from - */ -const ChapterSim = React.memo(() => { - const { value: textAssets } = useRequest(fetchTextAssets, []); - const { value: chapters } = useRequest(fetchChapters, []); - - const [chosenIndex, setChosenIndex] = React.useState(createChapterIndex); - - return ( - <> -

Chapter Simulator

- -
- - - ); -}); -export default ChapterSim; diff --git a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorCheckpointSim.tsx b/src/pages/academy/gameSimulator/subcomponents/GameSimulatorCheckpointSim.tsx deleted file mode 100644 index 8cb67bec53..0000000000 --- a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorCheckpointSim.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Button } from '@blueprintjs/core'; -import { useRequest } from 'src/commons/utils/Hooks'; -import SourceAcademyGame from 'src/features/game/SourceAcademyGame'; -import { fetchTextAssets } from 'src/features/gameSimulator/GameSimulatorService'; -import MainMenu from 'src/features/gameSimulator/scenes/MainMenu'; -import mainMenuConstants from 'src/features/gameSimulator/scenes/MainMenuConstants'; - -import CheckpointTxtLoader from './GameSimulatorCheckpointTxtLoader'; - -/** - * This component helps one simulate a checkpoint by - * supplying two txt files - the default txt file - * and the checkpoint txt file - * - * @param textAssets these are the list of text files on S3, if storywriter's simulation - * involves S3 text files. - */ -export default function CheckpointSim() { - const { value: textAssets } = useRequest(fetchTextAssets, []); - - function simulateCheckpoint() { - (SourceAcademyGame.getInstance().getCurrentSceneRef() as MainMenu).simulateCheckpoint(); - } - - return ( - <> -

Checkpoint Text Loader

- Step 1: Choose default checkpoint - - Step 2: Choose checkpoint text - -
- -
-
- -
- - ); -} - -function clearSessionStorage(e: any) { - sessionStorage.setItem(mainMenuConstants.gameTxtStorageName.checkpointTxt, ''); - sessionStorage.setItem(mainMenuConstants.gameTxtStorageName.defaultChapter, ''); -} diff --git a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorGame.ts b/src/pages/academy/gameSimulator/subcomponents/GameSimulatorGame.ts deleted file mode 100644 index a5cf2dbc78..0000000000 --- a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorGame.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as Phaser from 'phaser'; -import { screenSize } from 'src/features/game/commons/CommonConstants'; -import CheckpointTransition from 'src/features/game/scenes/checkpointTransition/CheckpointTransition'; -import GameManager from 'src/features/game/scenes/gameManager/GameManager'; -import SourceAcademyGame, { GameType } from 'src/features/game/SourceAcademyGame'; -import MainMenu from 'src/features/gameSimulator/scenes/MainMenu'; - -const config = { - debug: true, - type: Phaser.CANVAS, - width: screenSize.x, - height: screenSize.y, - physics: { - default: 'arcade' - }, - scale: { - mode: Phaser.Scale.FIT, - parent: 'game-display' - }, - fps: { - target: 24 - } -}; - -export const createGameSimulatorGame = () => { - const game = new SourceAcademyGame(config, GameType.Simulator); - game.scene.add('GameSimulatorMenu', MainMenu, true); - game.scene.add('GameManager', GameManager); - game.scene.add('CheckpointTransition', CheckpointTransition); - return game; -}; diff --git a/src/pages/academy/gameSimulator/subcomponents/assetViewer/AssetViewer.tsx b/src/pages/academy/gameSimulator/subcomponents/assetViewer/AssetViewer.tsx new file mode 100644 index 0000000000..668fd3e9b9 --- /dev/null +++ b/src/pages/academy/gameSimulator/subcomponents/assetViewer/AssetViewer.tsx @@ -0,0 +1,79 @@ +import { Icon, Tab, Tabs, Tooltip, Tree, TreeNodeInfo } from '@blueprintjs/core'; +import { cloneDeep } from 'lodash'; +import React from 'react'; +import { useRequest } from 'src/commons/utils/Hooks'; +import { fetchAssetPaths, s3AssetFolders } from 'src/features/gameSimulator/GameSimulatorService'; +import { deleteS3File } from 'src/features/gameSimulator/GameSimulatorService'; + +import AssetViewerPreview from './AssetViewerPreview'; +import AssetViewerUpload from './AssetViewerUpload'; +import { convertAssetPathsToTree, treeMap } from './AssetViewerUtils'; + +/** + * This component renders the Asset Viewer component in the Game Simulator. + * + * It provides a preview of all the S3 asset files in a document tree format. + * The selected asset will be available for preview. + */ +const AssetViewer: React.FC = () => { + const { value: assetPaths } = useRequest(fetchAssetPaths, []); + + const [currentAsset, setCurrentAsset] = React.useState(''); + const [assetTree, setAssetTree] = React.useState([]); + + React.useEffect(() => { + const deleteIcon = (filePath: string): JSX.Element => { + const deleteFile = async () => { + const confirm = window.confirm(`Are you sure you want to delete ${filePath}?`); + alert( + confirm + ? await deleteS3File(filePath) + : 'Please double check before deleting an asset!\nThere is NO undoing this action!' + ); + }; + return ( + + + + ); + }; + setAssetTree(convertAssetPathsToTree(assetPaths, deleteIcon, s3AssetFolders)); + }, [assetPaths]); + + const handleNodeClick = (nodeData: TreeNodeInfo, path: integer[]) => { + const selectedPath = nodeData.id.toString(); + if (!nodeData.childNodes) { + setCurrentAsset(selectedPath); + } + const newTree = cloneDeep(assetTree); + const originallySelected = Tree.nodeFromPath(path, newTree).isSelected; + treeMap(newTree, (node: TreeNodeInfo) => (node.isSelected = false)); + Tree.nodeFromPath(path, newTree).isSelected = + originallySelected === null ? true : !originallySelected; + Tree.nodeFromPath(path, newTree).isExpanded = !Tree.nodeFromPath(path, newTree).isExpanded; + setAssetTree(newTree); + }; + + return ( + <> +

View / Upload Assets

+ + + +
+
+ + + } + /> + } /> +
+ + ); +}; + +export default AssetViewer; diff --git a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorAssetViewer.tsx b/src/pages/academy/gameSimulator/subcomponents/assetViewer/AssetViewerPreview.tsx similarity index 59% rename from src/pages/academy/gameSimulator/subcomponents/GameSimulatorAssetViewer.tsx rename to src/pages/academy/gameSimulator/subcomponents/assetViewer/AssetViewerPreview.tsx index d75e726173..3eccc7113f 100644 --- a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorAssetViewer.tsx +++ b/src/pages/academy/gameSimulator/subcomponents/assetViewer/AssetViewerPreview.tsx @@ -1,23 +1,19 @@ import { memo } from 'react'; import { Constants } from 'src/features/game/commons/CommonConstants'; import { toS3Path } from 'src/features/game/utils/GameUtils'; - -type AssetProps = { - assetPath: string; -}; +import { AssetProps } from 'src/features/gameSimulator/GameSimulatorTypes'; /** - * This file renders one asset path so that story writers can preview - * the asset + * This component renders the asset corresponding to the given asset path. * - * @assetPath - the asset to render/preview + * @assetPath - The path of the asset to render / preview. */ -const AssetViewer = memo(({ assetPath }: AssetProps) => { +const AssetViewerPreview: React.FC = ({ assetPath }) => { const displayAssetPath = assetPath || Constants.defaultAssetPath; return ( asset { @@ -26,6 +22,6 @@ const AssetViewer = memo(({ assetPath }: AssetProps) => { }} /> ); -}); +}; -export default AssetViewer; +export default memo(AssetViewerPreview); diff --git a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorAssetFileUploader.tsx b/src/pages/academy/gameSimulator/subcomponents/assetViewer/AssetViewerUpload.tsx similarity index 55% rename from src/pages/academy/gameSimulator/subcomponents/GameSimulatorAssetFileUploader.tsx rename to src/pages/academy/gameSimulator/subcomponents/assetViewer/AssetViewerUpload.tsx index 8368b97d44..d817775966 100644 --- a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorAssetFileUploader.tsx +++ b/src/pages/academy/gameSimulator/subcomponents/assetViewer/AssetViewerUpload.tsx @@ -1,21 +1,16 @@ -import { Button, InputGroup, Menu, MenuItem, Position } from '@blueprintjs/core'; -import { Popover2 } from '@blueprintjs/popover2'; -import React from 'react'; +import { Button, InputGroup, Menu, MenuItem, Popover, Position } from '@blueprintjs/core'; +import React, { useState } from 'react'; import { s3AssetFolders, uploadAssetsToS3 } from 'src/features/gameSimulator/GameSimulatorService'; -const specifyFolderText = 'Specify own folder...'; -const folderOverwritePlaceholder = "Or specify your own, e.g. 'locations/hallway'"; - /** - * This is component allows storywriters to upload any assets into to - * specific folders into Game Sim's Asset Uploader + * This component allows uploading of new assets into the S3 asset files. */ -const AssetFileUploader = () => { - const [fileList, setFileList] = React.useState(); - const [uploadFolder, setUploadFolder] = React.useState(s3AssetFolders[0]); +const AssetViewerUpload: React.FC = () => { + const [fileList, setFileList] = useState(); + const [uploadFolder, setUploadFolder] = useState(s3AssetFolders[0]); - const [folderOverwrite, setFolderOverwrite] = React.useState(); - const [showfolderOverwrite, setShowFolderOverwrite] = React.useState(false); + const [folderOverwrite, setFolderOverwrite] = useState(); + const [showfolderOverwrite, setShowFolderOverwrite] = useState(false); function handleLoadFile(e: any) { if (!e.target.files) return; @@ -51,32 +46,28 @@ const AssetFileUploader = () => { {s3AssetFolders.map(folder => ( ))} - + ); return (
-

Choose Folder

- +

Choose a Folder to upload an asset to:

+
); }; -export default AssetFileUploader; +export default AssetViewerUpload; diff --git a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorAssetSelectionHelper.tsx b/src/pages/academy/gameSimulator/subcomponents/assetViewer/AssetViewerUtils.tsx similarity index 57% rename from src/pages/academy/gameSimulator/subcomponents/GameSimulatorAssetSelectionHelper.tsx rename to src/pages/academy/gameSimulator/subcomponents/assetViewer/AssetViewerUtils.tsx index fab135ff5e..3f05682c9a 100644 --- a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorAssetSelectionHelper.tsx +++ b/src/pages/academy/gameSimulator/subcomponents/assetViewer/AssetViewerUtils.tsx @@ -1,48 +1,38 @@ import { TreeNodeInfo } from '@blueprintjs/core'; -import _ from 'lodash'; +import { set } from 'lodash'; /** - * This function applies a function fn to every node in a blueprint core Tree + * This function takes a list of filepaths and returns blueprint core tree nodes. + * Each node represents a file / folder. * - * @param nodes All parent nodes of the blueprint core tree - * @param fn Function to apply to every element in the tree - */ -export function treeMap(nodes: TreeNodeInfo[] | undefined, fn: (node: TreeNodeInfo) => void) { - nodes && - nodes.forEach(node => { - fn(node); - treeMap(node.childNodes, fn); - }); -} - -/** - * This function takes a list of filepaths - * (e.g. ["locations/hallway/normal.png", "locations/hallway/emergency.png"]) and returns - * blueprint core tree nodes, where each node represents a folder/file. + * Example of list of filepaths: ["locations/hallway/normal.png", "locations/hallway/emergency.png"] * - * The child of each folder node are the files/folders inside it. + * The child of each folder node are the files / folders inside it. * - * @param assetPaths - a list of all filepaths + * @param assetPaths - A list of all filepaths. * @param iconRenderer - Function that dictates what JSX Element/icon to render beside - * all blueprint core nodes basded on the file path - * @param rootFolders - a default list of parent folder names that you want to display regardless of - * whether or not they have contents + * all blueprint core nodes basded on the file path. + * @param rootFolders - A default list of parent folder names that you want to display regardless of + * whether or not they have contents. * @returns {TreeNodeInfo[]} - a blueprint core tree parent nodes */ -export function assetPathsToTree( +export function convertAssetPathsToTree( assetPaths: string[], iconRenderer: (pathName: string) => JSX.Element, rootFolders: string[] = [] ): TreeNodeInfo[] { const assetObj = {}; - assetPaths.forEach(assetPath => _.set(assetObj, assetPath.split('/'), 'FILE')); + assetPaths.forEach(assetPath => set(assetObj, assetPath.split('/'), 'FILE')); rootFolders.forEach(folder => { if (!assetObj[folder] || assetObj[folder] === 'FILE') { assetObj[folder] = []; } }); - function helper(parentFolders: string[], assetObj: object | Array): TreeNodeInfo[] { + const convertAssetObjectsToTree = ( + parentFolders: string[], + assetObj: object | Array + ): TreeNodeInfo[] => { return Object.keys(assetObj).map(file => { const shortPath = '/' + parentFolders.join('/') + '/' + file; return { @@ -50,9 +40,25 @@ export function assetPathsToTree( label: file, secondaryLabel: iconRenderer(shortPath), childNodes: - assetObj[file] === 'FILE' ? undefined : helper([...parentFolders, file], assetObj[file]) + assetObj[file] === 'FILE' + ? undefined + : convertAssetObjectsToTree([...parentFolders, file], assetObj[file]) }; }); - } - return helper([], assetObj); + }; + return convertAssetObjectsToTree([], assetObj); +} + +/** + * This function applies a function fn to every node in a blueprint core Tree + * + * @param nodes All parent nodes of the blueprint core tree + * @param fn Function to apply to every element in the tree + */ +export function treeMap(nodes: TreeNodeInfo[] | undefined, fn: (node: TreeNodeInfo) => void) { + nodes && + nodes.forEach(node => { + fn(node); + treeMap(node.childNodes, fn); + }); } diff --git a/src/pages/academy/gameSimulator/subcomponents/chapterPublisher/ChapterPublisher.tsx b/src/pages/academy/gameSimulator/subcomponents/chapterPublisher/ChapterPublisher.tsx new file mode 100644 index 0000000000..e643f42bed --- /dev/null +++ b/src/pages/academy/gameSimulator/subcomponents/chapterPublisher/ChapterPublisher.tsx @@ -0,0 +1,44 @@ +import { memo, useState } from 'react'; +import { useRequest } from 'src/commons/utils/Hooks'; +import { defaultChapter } from 'src/features/gameSimulator/GameSimulatorConstants'; +import { fetchChapters, fetchTextAssets } from 'src/features/gameSimulator/GameSimulatorService'; +import { ChapterDetail } from 'src/features/gameSimulator/GameSimulatorTypes'; + +import ChapterPublisherEditor from './ChapterPublisherEditor'; + +/** + * This components renders the Chapter Publisher component in the Game Simulator. + * + * @param textAssets - List of all text assets on S3 to choose from. + */ +const ChapterPublisher: React.FC = () => { + const { value: textAssets } = useRequest(fetchTextAssets, []); + const { value: chapters } = useRequest(fetchChapters, []); + + const [chosenIndex, setChosenIndex] = useState(-1); + + return ( + <> +

Publish / Edit Chapters

+ +
+
+
+ + + ); +}; + +export default memo(ChapterPublisher, () => true); diff --git a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorChapterEditor.tsx b/src/pages/academy/gameSimulator/subcomponents/chapterPublisher/ChapterPublisherEditor.tsx similarity index 55% rename from src/pages/academy/gameSimulator/subcomponents/GameSimulatorChapterEditor.tsx rename to src/pages/academy/gameSimulator/subcomponents/chapterPublisher/ChapterPublisherEditor.tsx index fb53c28a09..27e30433ce 100644 --- a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorChapterEditor.tsx +++ b/src/pages/academy/gameSimulator/subcomponents/chapterPublisher/ChapterPublisherEditor.tsx @@ -1,54 +1,44 @@ import { Button, Intent, Switch } from '@blueprintjs/core'; import { DatePicker } from '@blueprintjs/datetime'; -import React from 'react'; +import { memo, useCallback, useEffect, useState } from 'react'; import { getStandardDateTime } from 'src/commons/utils/DateHelper'; import { useInput } from 'src/commons/utils/Hooks'; import { SortableList, useSortableList } from 'src/commons/utils/SortableList'; -import SourceAcademyGame from 'src/features/game/SourceAcademyGame'; -import { toS3Path } from 'src/features/game/utils/GameUtils'; -import { callGameManagerForSim } from 'src/features/game/utils/TxtLoaderUtils'; +import { defaultChapter } from 'src/features/gameSimulator/GameSimulatorConstants'; import { deleteChapterRequest, updateChapterRequest } from 'src/features/gameSimulator/GameSimulatorService'; -import { ChapterDetail } from 'src/features/gameSimulator/GameSimulatorTypes'; - -import { createChapterIndex, inAYear } from './GameSimulatorChapterSim'; - -type ChapterSimProps = { - chapterDetail: ChapterDetail; - checkpointFilenames?: string[]; -}; +import { ChapterSimProps } from 'src/features/gameSimulator/GameSimulatorTypes'; +import { dateOneYearFromNow } from 'src/features/gameSimulator/GameSimulatorUtils'; /** - * This is the Chapter Editor Form that - * storywriters use to either create - * or udpate chapters for the game. + * This component renders the Chapter Publishing form to create new chapters. * - * @param chapterDetail the starting state of the form, - * either loaded from defaultChapter if user wants to create a new chapter - * or loaded from the existing chapter if user wants to edit the chapter - * @param checkpointFilenames the list of all checkpoint text files to choose from + * @param chapterDetail The starting state of the form, either loaded from defaultChapter + * if the user wants to create a new chapter, or loaded from + * existing chapters if user wants to edit the selected chapter. + * @param chapterFilenames List of all text asset filenames on S3 to choose from. */ -const ChapterEditor = React.memo(({ chapterDetail, checkpointFilenames }: ChapterSimProps) => { +const ChapterPublisherEditor: React.FC = ({ chapterDetail, chapterFilenames }) => { const { id } = chapterDetail; const { value: title, setValue: setTitle, inputProps: titleProps } = useInput(''); const { value: imageUrl, setValue: setImageUrl, inputProps: imageUrlProps } = useInput(''); const { items: chosenFiles, setItems: setChosenFiles, onSortEnd } = useSortableList(); - const [isPublished, setIsPublished] = React.useState(false); - const [openDate, setOpenDate] = React.useState(new Date()); - const [txtsNotChosen, setTxtsNotChosen] = React.useState([]); - const [rerender, setRender] = React.useState(false); + const [isPublished, setIsPublished] = useState(false); + const [openDate, setOpenDate] = useState(new Date()); + const [txtsNotChosen, setTxtsNotChosen] = useState([]); + const [rerender, setRender] = useState(false); - React.useEffect(() => { + useEffect(() => { setTitle(chapterDetail.title); setImageUrl(chapterDetail.imageUrl); setOpenDate(new Date(chapterDetail.openAt)); setChosenFiles(chapterDetail.filenames); setIsPublished(chapterDetail.isPublished); setTxtsNotChosen( - (checkpointFilenames || []).filter(textAsset => !chapterDetail.filenames.includes(textAsset)) + (chapterFilenames || []).filter(textAsset => !chapterDetail.filenames.includes(textAsset)) ); }, [ chapterDetail, @@ -56,13 +46,13 @@ const ChapterEditor = React.memo(({ chapterDetail, checkpointFilenames }: Chapte setImageUrl, setOpenDate, setTitle, - checkpointFilenames, + chapterFilenames, rerender ]); const deleteAllFromChosen = () => chosenFiles.map(deleteFileFromChosen); - const deleteFileFromChosen = React.useCallback( + const deleteFileFromChosen = useCallback( (txtFile: string) => { setChosenFiles(prevItemList => prevItemList.filter(item => item !== txtFile)); setTxtsNotChosen(prevItemList => [...prevItemList, txtFile]); @@ -70,7 +60,7 @@ const ChapterEditor = React.memo(({ chapterDetail, checkpointFilenames }: Chapte [setChosenFiles] ); - const addFileToChosen = React.useCallback( + const addFileToChosen = useCallback( (txtFile: string) => { setChosenFiles(prevItemList => [...prevItemList, txtFile]); setTxtsNotChosen(prevItemList => prevItemList.filter(item => item !== txtFile)); @@ -81,7 +71,7 @@ const ChapterEditor = React.memo(({ chapterDetail, checkpointFilenames }: Chapte const saveChapter = async () => { const updatedChapter = { openAt: openDate.toISOString(), - closeAt: inAYear(openDate).toISOString(), + closeAt: dateOneYearFromNow(openDate).toISOString(), title, filenames: chosenFiles, imageUrl, @@ -89,13 +79,15 @@ const ChapterEditor = React.memo(({ chapterDetail, checkpointFilenames }: Chapte }; const confirm = window.confirm( - `Are you sure you want to save changes to ${JSON.stringify(updatedChapter)}` + `Are you sure you want to save changes to Chapter ${id}: ${title}?\n\nChapter Information: ${JSON.stringify( + updatedChapter + )}` ); if (!confirm) { return; } const response = - parseInt(id) === createChapterIndex + parseInt(id) === defaultChapter.id ? await updateChapterRequest('', { story: updatedChapter }) : await updateChapterRequest(id, { story: updatedChapter }); @@ -103,7 +95,7 @@ const ChapterEditor = React.memo(({ chapterDetail, checkpointFilenames }: Chapte }; const deleteChapter = async () => { - const confirm = window.confirm('Are you sure you want to delete this chapter?'); + const confirm = window.confirm(`Are you sure you want to delete Chapter ${id}: ${title}?`); if (confirm) { const response = await deleteChapterRequest(id); alert(response); @@ -111,44 +103,47 @@ const ChapterEditor = React.memo(({ chapterDetail, checkpointFilenames }: Chapte }; const clearChanges = () => { - const confirm = window.confirm('Are you you want to clear changes for this chapter?'); + const confirm = window.confirm( + `Are you sure you want to clear changes for Chapter ${id}: ${title}?` + ); if (confirm) { setRender(!rerender); alert('Cleared changes'); } }; - const simulateChapter = async () => { - SourceAcademyGame.getInstance().setChapterSimStack(chosenFiles); - await callGameManagerForSim(); - }; - return ( <>

- Title: + Title:

Open date: {openDate && getStandardDateTime(openDate.toISOString())} { + onChange={(date: Date | null) => { date && setOpenDate(date); }} + showActionsBar + highlightCurrentDay />

- Image url: - + Chapter Preview Image URL:

- Checkpoint Txt Files +

Chapter Files (.txt):

-
- {chosenFiles.length > 0 && ( - + {chosenFiles.length > 0 ? ( + <> +
+ +
+ + ) : ( +

No file has been selected yet.

)}
- All Txt Files +

All Available Chapter Files (.txt)

{txtsNotChosen && txtsNotChosen.map(textFile => { return ( @@ -160,14 +155,11 @@ const ChapterEditor = React.memo(({ chapterDetail, checkpointFilenames }: Chapte ); })}
- -
+

setIsPublished(!isPublished)} /> @@ -181,6 +173,6 @@ const ChapterEditor = React.memo(({ chapterDetail, checkpointFilenames }: Chapte ); -}); +}; -export default ChapterEditor; +export default memo(ChapterPublisherEditor); diff --git a/src/pages/academy/gameSimulator/subcomponents/chapterSimulator/ChapterSimulator.tsx b/src/pages/academy/gameSimulator/subcomponents/chapterSimulator/ChapterSimulator.tsx new file mode 100644 index 0000000000..8c9b348abd --- /dev/null +++ b/src/pages/academy/gameSimulator/subcomponents/chapterSimulator/ChapterSimulator.tsx @@ -0,0 +1,60 @@ +import { Button } from '@blueprintjs/core'; +import { useRequest } from 'src/commons/utils/Hooks'; +import SourceAcademyGame from 'src/features/game/SourceAcademyGame'; +import { gameSimulatorMenuConstants } from 'src/features/gameSimulator/GameSimulatorConstants'; +import GameSimulatorMenu from 'src/features/gameSimulator/GameSimulatorMenu'; +import { fetchTextAssets } from 'src/features/gameSimulator/GameSimulatorService'; + +import ChapterSimulatorTextLoader from './ChapterSimulatorTextLoader'; + +/** + * This component renders the Chapter Simulator component in the Game Simulator. + * + * It will simulate a game chapter using the given text files (from either S3 or Local). + * + * @param textAssets List of all text assets on S3 to choose from. + */ +const ChapterSimulator: React.FC = () => { + const { value: textAssets } = useRequest(fetchTextAssets, []); + + function simulateCheckpoint() { + ( + SourceAcademyGame.getInstance().getCurrentSceneRef() as GameSimulatorMenu + ).simulateCheckpoint(); + } + + function clearSessionStorage(e: any) { + sessionStorage.setItem(gameSimulatorMenuConstants.gameTxtStorageName.checkpointTxt, ''); + sessionStorage.setItem(gameSimulatorMenuConstants.gameTxtStorageName.defaultChapter, ''); + } + + return ( + <> +

Simulate Chapters

+ Choose the Main Chapter file: + +
+
+ Choose a Default Variables ("Default Checkpoint") file (Optional): + +
+
+
+ +
+
+ +
+ + ); +}; + +export default ChapterSimulator; diff --git a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorCheckpointTxtLoader.tsx b/src/pages/academy/gameSimulator/subcomponents/chapterSimulator/ChapterSimulatorTextLoader.tsx similarity index 53% rename from src/pages/academy/gameSimulator/subcomponents/GameSimulatorCheckpointTxtLoader.tsx rename to src/pages/academy/gameSimulator/subcomponents/chapterSimulator/ChapterSimulatorTextLoader.tsx index 90a33ea93a..b5a7c64c03 100644 --- a/src/pages/academy/gameSimulator/subcomponents/GameSimulatorCheckpointTxtLoader.tsx +++ b/src/pages/academy/gameSimulator/subcomponents/chapterSimulator/ChapterSimulatorTextLoader.tsx @@ -1,25 +1,23 @@ import 'ace-builds/webpack-resolver'; import { Button, Tab, Tabs } from '@blueprintjs/core'; -import React from 'react'; +import { useState } from 'react'; import { toTxtPath } from 'src/features/game/assets/TextAssets'; import { toS3Path } from 'src/features/game/utils/GameUtils'; - -type Props = { - storageName: string; - s3TxtFiles: string[]; -}; +import { StorageProps } from 'src/features/gameSimulator/GameSimulatorTypes'; +import { + createHeadersWithCors, + loadFileLocally +} from 'src/features/gameSimulator/GameSimulatorUtils'; /** - * This component enables story writers to upload their txt file contents - * to the browser, or load a file from S3 and store the txt contents - * in the browser. So that GameManager can read from these txt files + * This component allows chapter text files to be loaded either from S3, or from the user's local device. * - * @param storageName the field in browser storage where the loaded/fetched txt files get stored temporarily - * @param s3TxtFiles the list of S3 txt files to choose from + * @param storageName The field within browser storage to temporarily store the loaded / fetched text file(s). + * @param s3TxtFiles List of all text assets on S3 to choose from. */ -function CheckpointTxtLoader({ storageName, s3TxtFiles }: Props) { - const [chosenFilename, setChosenFilename] = React.useState(s3TxtFiles[0]); +const ChapterSimulatorTextLoader: React.FC = ({ storageName, s3TxtFiles }) => { + const [chosenFilename, setChosenFilename] = useState(s3TxtFiles[0]); function onLoadTxt(e: any) { if (!e.target.files) return; @@ -41,7 +39,7 @@ function CheckpointTxtLoader({ storageName, s3TxtFiles }: Props) { const chooseS3Txt = ( <> - {s3TxtFiles.map(file => (
); -} - -const loadFileLocally = (storageName: string, txtFile: File) => { - const reader = new FileReader(); - reader.readAsText(txtFile); - reader.onloadend = _ => { - if (!reader.result) { - return; - } - sessionStorage.setItem(storageName, reader.result.toString()); - }; }; -export default CheckpointTxtLoader; - -function createHeadersWithCors(): Headers { - const headers = new Headers(); - headers.append('Access-Control-Allow-Origin', '*'); - return headers; -} +export default ChapterSimulatorTextLoader;