Skip to content

Commit f5d83cc

Browse files
authored
Generate story with information from backend (#250)
* Fetch grade and story from GET /user * Dynamically import game * Fix some broken navigations * Refresh a user's story on page refresh * Rebase off master * Change state.session.story according to backend changes * Change start mission hook to redirect only to listing * Bump version v0.1.7 -> v0.1.8 * Fix runtime error on refresh to /academy/game on mock data * Fix issue nesting div tag in p tag * Use session.state.grade in profile dropdown * Fix unconventional file names for dropdown/*
1 parent c3145d1 commit f5d83cc

File tree

23 files changed

+126
-90
lines changed

23 files changed

+126
-90
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"private": true,
33
"name": "cadet-frontend",
4-
"version": "0.1.7",
4+
"version": "0.1.8",
55
"scripts-info": {
66
"format": "Format source code",
77
"start": "Start the Webpack development server",

src/actions/actionTypes.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,8 @@ export const FETCH_ASSESSMENT_OVERVIEWS = 'FETCH_ASSESSMENT_OVERVIEWS'
5252
export const FETCH_GRADING = 'FETCH_GRADING'
5353
export const FETCH_GRADING_OVERVIEWS = 'FETCH_GRADING_OVERVIEWS'
5454
export const LOGIN = 'LOGIN'
55-
export const SET_ROLE = 'SET_ROLE'
5655
export const SET_TOKENS = 'SET_TOKENS'
57-
export const SET_USERNAME = 'SET_USERNAME'
56+
export const SET_USER = 'SET_USER'
5857
export const SUBMIT_ANSWER = 'SUBMIT_ANSWER'
5958
export const SUBMIT_ASSESSMENT = 'SUBMIT_ASSESSMENT'
6059
export const SUBMIT_GRADING = 'SUBMIT_GRADING'

src/actions/session.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ActionCreator } from 'redux'
22

33
import { Grading, GradingOverview } from '../components/academy/grading/gradingShape'
44
import { IAssessment, IAssessmentOverview } from '../components/assessment/assessmentShape'
5+
import { Story } from '../reducers/states'
56
import * as actionTypes from './actionTypes'
67

78
import { Role } from '../reducers/states'
@@ -37,11 +38,6 @@ export const login = () => ({
3738
type: actionTypes.LOGIN
3839
})
3940

40-
export const setRole: ActionCreator<actionTypes.IAction> = (role: Role) => ({
41-
type: actionTypes.SET_ROLE,
42-
payload: role
43-
})
44-
4541
export const setTokens: ActionCreator<actionTypes.IAction> = ({ accessToken, refreshToken }) => ({
4642
type: actionTypes.SET_TOKENS,
4743
payload: {
@@ -50,9 +46,14 @@ export const setTokens: ActionCreator<actionTypes.IAction> = ({ accessToken, ref
5046
}
5147
})
5248

53-
export const setUsername: ActionCreator<actionTypes.IAction> = (username: string) => ({
54-
type: actionTypes.SET_USERNAME,
55-
payload: username
49+
export const setUser: ActionCreator<actionTypes.IAction> = (user: {
50+
name: string
51+
role: Role
52+
grade: number
53+
story: Story
54+
}) => ({
55+
type: actionTypes.SET_USER,
56+
payload: user
5657
})
5758

5859
export const submitAnswer: ActionCreator<actionTypes.IAction> = (

src/components/Application.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface IStateProps {
1919
currentPlaygroundChapter: number
2020
role?: Role
2121
title: string
22-
username?: string
22+
name?: string
2323
currentPlaygroundExternalLibrary: ExternalLibraryName
2424
}
2525

@@ -42,7 +42,7 @@ class Application extends React.Component<IApplicationProps, {}> {
4242
<NavigationBar
4343
handleLogOut={this.props.handleLogOut}
4444
role={this.props.role}
45-
username={this.props.username}
45+
name={this.props.name}
4646
title={this.props.title}
4747
/>
4848
<div className="Application__main">

src/components/NavigationBar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface INavigationBarProps {
1717
handleLogOut: () => void
1818
role?: Role
1919
title: string
20-
username?: string
20+
name?: string
2121
}
2222

2323
const NavigationBar: React.SFC<INavigationBarProps> = props => (
@@ -68,7 +68,7 @@ const NavigationBar: React.SFC<INavigationBarProps> = props => (
6868
<NavbarDivider className="default-divider" />
6969
</div>
7070

71-
<Dropdown handleLogOut={props.handleLogOut} username={props.username} />
71+
<Dropdown handleLogOut={props.handleLogOut} name={props.name} />
7272
</NavbarGroup>
7373
</Navbar>
7474
)

src/components/__tests__/__snapshots__/Application.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
exports[`Application renders correctly 1`] = `
44
"<div className=\\"Application\\">
5-
<NavigationBar handleLogOut={[Function: handleLogOut]} role={[undefined]} username={[undefined]} title=\\"Cadet\\" />
5+
<NavigationBar handleLogOut={[Function: handleLogOut]} role={[undefined]} name={[undefined]} title=\\"Cadet\\" />
66
<div className=\\"Application__main\\">
77
<Switch>
88
<Route path=\\"/academy\\" component={[Function]} />

src/components/__tests__/__snapshots__/NavigationBar.tsx.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ exports[`NavigationBar renders "Not logged in" correctly 1`] = `
3535
<div className=\\"hidden-xs\\">
3636
<Blueprint2.NavbarDivider className=\\"default-divider\\" />
3737
</div>
38-
<Dropdown handleLogOut={[Function: handleLogOut]} username={[undefined]} />
38+
<Dropdown handleLogOut={[Function: handleLogOut]} name={[undefined]} />
3939
</Blueprint2.NavbarGroup>
4040
</Blueprint2.Navbar>"
4141
`;
@@ -75,7 +75,7 @@ exports[`NavigationBar renders correctly with username 1`] = `
7575
<div className=\\"hidden-xs\\">
7676
<Blueprint2.NavbarDivider className=\\"default-divider\\" />
7777
</div>
78-
<Dropdown handleLogOut={[Function: handleLogOut]} username=\\"Evis Rucer\\" />
78+
<Dropdown handleLogOut={[Function: handleLogOut]} name={[undefined]} />
7979
</Blueprint2.NavbarGroup>
8080
</Blueprint2.Navbar>"
8181
`;

src/components/academy/game/create-initializer.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@ export default function (StoryXMLPlayer, story, username, attemptedAll) {
77

88
var hookHandlers = {
99
startMission: function (number) {
10+
console.log('startMission: ' + number)
1011
const assessmentType = story.split('-')[0] + 's'
11-
history.push(`/academy/#{assessmentType}/#{number}`)
12+
return history.push('/academy/' + assessmentType)
13+
// TODO: Reimplement redirection to actual assessment rather than the
14+
// listing, after story.xml files have been changed. Currently, the
15+
// story xml number points to the mission number, but the
16+
// assessment id we obtain and therefore organise our assessments
17+
// by refers to the database table ID
18+
// return history.push('/academy/' + assessmentType + '/' + number)
1219
},
1320
openTemplate: function (name) {
1421
switch (name) {
@@ -19,7 +26,7 @@ export default function (StoryXMLPlayer, story, username, attemptedAll) {
1926
case 'lesson_plan':
2027
return history.push('/academy/missions');
2128
case 'students':
22-
return history.push('/profile');
29+
return history.push('/news');
2330
case 'materials':
2431
return history.push('/material');
2532
case 'IDE':
@@ -40,7 +47,7 @@ export default function (StoryXMLPlayer, story, username, attemptedAll) {
4047
};
4148

4249
function openWristDevice() {
43-
history.push('/academy/announements')
50+
history.push('/academy/news')
4451
}
4552

4653
function startGame(div, canvas, saveData) {
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import createInitializer from './create-initializer'
22

3-
export default function(div, canvas, story) {
3+
export default function(div, canvas, username, story, attemptedAll) {
44
window.ASSETS_HOST =
55
'https://s3-ap-southeast-1.amazonaws.com/source-academy-assets/';
66
var StoryXMLPlayer = require('./story-xml-player');
77
var container = document.getElementById('game-display')
8-
var username = container.getAttribute('data-username')
9-
var attemptedAll = container.getAttribute('data-attempted-all') === "true"
108
var initialize = createInitializer(StoryXMLPlayer, story, username, attemptedAll)
119
initialize(div, canvas);
1210
}

src/components/academy/game/index.tsx

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import * as React from 'react'
22

3-
import story from './game.js'
3+
import { setUser } from '../../../actions'
4+
import { store } from '../../../createStore'
5+
import { Story } from '../../../reducers/states'
6+
import { getUser } from '../../../sagas/backend'
47

58
type GameProps = DispatchProps & StateProps
69

@@ -10,45 +13,67 @@ export type DispatchProps = {
1013

1114
export type StateProps = {
1215
canvas?: HTMLCanvasElement
13-
storyAct: string
16+
name: string
17+
story?: Story
1418
}
1519

1620
export class Game extends React.Component<GameProps, {}> {
1721
private canvas: HTMLCanvasElement
1822
private div: HTMLDivElement
1923

20-
public componentDidMount() {
21-
/**
22-
* Basically, if the function story is called twice (on different canvas
23-
* elements), the second time the component is mounted, the pixi.js canvas
24-
* will show nothing but a black screen. This means that if the user
25-
* navigate aways from the game tab, and then back again, the game would not
26-
* work.
27-
*
28-
* So, we save a reference to the first canvas that is loaded. Thereafter,
29-
* when this component is mounted, use that canvas instead of the new canvas
30-
* mounted with this div. This is a bit hacky, and refs aren't favoured in
31-
* react, but it also prevents excessive loading of the game
32-
*/
24+
/**
25+
* Basically, if the function story is called twice (on different canvas
26+
* elements), the second time the component is mounted, the pixi.js canvas
27+
* will show nothing but a black screen. This means that if the user
28+
* navigate aways from the game tab, and then back again, the game would not
29+
* work.
30+
*
31+
* So, we save a reference to the first canvas that is loaded. Thereafter,
32+
* when this component is mounted, use that canvas instead of the new canvas
33+
* mounted with this div. This is a bit hacky, and refs aren't favoured in
34+
* react, but it also prevents excessive loading of the game
35+
*
36+
* Note that the story/4's 4th param is named 'attemptedAll'. It is true if a
37+
* storyline should not be loaded, and false if it should. In contrast,
38+
* backend sends us 'playStory', which is the negation (!) of `attemptedAll`.
39+
*/
40+
public async componentDidMount() {
41+
const story: any = (await import('./game.js')).default
42+
let storyOpts: Array<string | boolean>
3343
if (this.props.canvas === undefined) {
34-
story(this.div, this.canvas, this.props.storyAct)
44+
// First time rendering the Game component
45+
if (this.props.story) {
46+
storyOpts = [this.props.story.story, !this.props.story.playStory]
47+
} else {
48+
// session.story is undefined if creating store from localStorage
49+
const state = store.getState()
50+
const tokens = {
51+
accessToken: state.session.accessToken!,
52+
refreshToken: state.session.refreshToken!
53+
}
54+
const user: any = await getUser(tokens)
55+
if (user) {
56+
storyOpts = [user.story.story, !user.story.playStory]
57+
store.dispatch(setUser(user))
58+
} else {
59+
// if user is null, actions.logOut is called anyways; nonetheless we
60+
// set storyOpts, otherwise typescript complains about using storyOpts
61+
// before assignment in story/4 below
62+
storyOpts = ['mission-1', true]
63+
}
64+
}
65+
story(this.div, this.canvas, this.props.name, ...storyOpts)
3566
this.props.handleSaveCanvas(this.canvas)
3667
} else {
68+
// This browser window has loaded the Game component & canvas before
3769
this.div.innerHTML = ''
3870
this.div.appendChild(this.props.canvas)
3971
}
4072
}
4173

4274
public render() {
4375
return (
44-
<div
45-
id="game-display"
46-
className="sa-game"
47-
data-story="spaceship"
48-
data-attempted-all="true"
49-
data-username="mockUsername"
50-
ref={e => (this.div = e!)}
51-
>
76+
<div id="game-display" className="sa-game" ref={e => (this.div = e!)}>
5277
<canvas ref={e => (this.canvas = e!)} />
5378
</div>
5479
)

0 commit comments

Comments
 (0)