Index.jsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import { h, render, Component } from 'preact'; // eslint-disable-line no-unused-vars
  2. // content and config:
  3. import content from '../content/module.json';
  4. import config from '../config';
  5. // services and helper:
  6. import api from '../utilities/api';
  7. import { fixedDigits } from '../utilities/formatter';
  8. // screens and items:
  9. import TitleScreen from './TitleScreen.jsx';
  10. import FinalScreen from './FinalScreen.jsx';
  11. import QuestionScreen from './QuestionScreen.jsx';
  12. /**
  13. * titlescreen, questionscreen (repeat), finalscreen
  14. */
  15. export default class App extends Component {
  16. // construct and initialize functions
  17. constructor (props) {
  18. super(props);
  19. // for example: (1400 - 200) / 3 -> 400 points per section
  20. const diff = (config.numberOfPoints.first - config.numberOfPoints.last) / 3;
  21. // for example: 1400, 1000, 600, 200
  22. this.numberOfPoints = [
  23. parseInt(config.numberOfPoints.first, 10),
  24. parseInt(config.numberOfPoints.first - diff, 10),
  25. parseInt(config.numberOfPoints.last + diff, 10),
  26. parseInt(config.numberOfPoints.last, 10)
  27. ];
  28. this.state = {
  29. route: 'titlescreen', // current screen
  30. isFetching: false, // currently performing XHR?
  31. userId: null, // current user id for posting
  32. token: null, // current access token
  33. mode: 'intro', // current mode: init || intro || question || score
  34. currentQuestion: 0, // current question number
  35. currentAnswerIndex: null, // index in question pair of the currently set answer
  36. currentAnswerCorrect: false, // is the currently set question the correct one?
  37. currentNumberOfPoints: this.numberOfPoints[0], // current number of points
  38. nextNumberOfPoints: this.numberOfPoints[1], // next number of points
  39. currentAmount: config.highlightedAmount, // curent amount of points to be highlighted
  40. voteRatios: null // get votes from other users
  41. };
  42. if (window.location.hash) this.state.route = window.location.hash.replace('#', '');
  43. // context binding
  44. this.navigate = this.navigate.bind(this);
  45. this.toNextQuestion = this.toNextQuestion.bind(this);
  46. this.setAnswer = this.setAnswer.bind(this);
  47. this.getUserVotes = this.getUserVotes.bind(this);
  48. this.reset = this.reset.bind(this);
  49. this.setup = this.setup.bind(this);
  50. this.endUserSession = this.endUserSession.bind(this);
  51. }
  52. // to next question
  53. toNextQuestion () {
  54. // intro screen, introduce bubbles :P
  55. if (this.state.mode === 'intro') {
  56. this.setState({
  57. // currentQuestion: null,
  58. mode: 'init',
  59. route: 'question'
  60. });
  61. } else if (this.state.currentQuestion === 0 && this.state.mode === 'init') {
  62. // is first question?
  63. this.setState({
  64. currentAmount: 0,
  65. mode: 'question',
  66. route: 'question'
  67. });
  68. } else if (this.state.currentQuestion === 3 && this.state.mode === 'score') {
  69. // is last question? go to final screen
  70. this.setState({
  71. route: 'finalscreen',
  72. mode: 'intro',
  73. currentNumberOfPoints: this.numberOfPoints[0],
  74. currentAmount: config.highlightedAmount,
  75. currentQuestion: 0,
  76. currentAnswerIndex: null,
  77. currentAnswerCorrect: false,
  78. nextNumberOfPoints: this.numberOfPoints[1],
  79. voteRatios: null
  80. });
  81. } else if (this.state.voteRatios && this.state.mode === 'question') {
  82. // go to score screen
  83. this.setState({
  84. route: 'question',
  85. mode: 'score'
  86. });
  87. } else {
  88. // go to next question
  89. this.setState({
  90. route: 'question',
  91. mode: 'question',
  92. currentQuestion: this.state.currentQuestion + 1,
  93. currentAnswerIndex: null,
  94. currentNumberOfPoints: this.numberOfPoints[this.state.currentQuestion + 1],
  95. nextNumberOfPoints: this.numberOfPoints[this.state.currentQuestion + 2],
  96. currentAmount: 0,
  97. voteRatios: null
  98. });
  99. }
  100. }
  101. // set and check answer
  102. setAnswer (index, isCorrect) {
  103. if (this.state.currentAnswerIndex === null) {
  104. // check answer
  105. this.setState({
  106. currentAnswerIndex: index,
  107. currentAnswerCorrect: isCorrect,
  108. mode: 'question'
  109. });
  110. // submit answer
  111. if (!this.props.isOffline) {
  112. // only send answer for current question
  113. const payload = { userId: this.state.userId };
  114. payload[`answerQ${this.state.currentQuestion + 1}`] = this.state.currentAnswerIndex + 1;
  115. api.post(config.api.create, payload, this.state.token).then(() => this.getUserVotes());
  116. } else {
  117. this.getUserVotes();
  118. }
  119. }
  120. }
  121. // get user votes from api
  122. getUserVotes () {
  123. if (!this.props.isOffline) {
  124. api.get(config.api.proportions, { question: this.state.currentQuestion + 1 }).then(
  125. json => this.setState({ voteRatios: [ json.anteile.richtig, json.anteile.falsch ] })
  126. );
  127. } else {
  128. this.setState({ voteRatios: [] });
  129. }
  130. }
  131. // initial setup
  132. setup () {
  133. if (!this.props.isOffline) {
  134. this.setState({ isFetching: true });
  135. api.getToken().then(accessToken => {
  136. this.setState({ token: accessToken });
  137. // create user
  138. api.createUser(accessToken)
  139. .then(user => {
  140. this.setState({ userId: user.userId, isFetching: false });
  141. this.jumpTo();
  142. });
  143. });
  144. }
  145. }
  146. // shortcut: jump to specific screen using `#{something}`
  147. jumpTo () {
  148. if (window.location.hash) {
  149. const hashInfo = window.location.hash.replace('#', '').split('_');
  150. this.setState({
  151. route: 'question',
  152. mode: hashInfo[0],
  153. currentAmount: 0,
  154. currentAnswerCorrect: null,
  155. currentAnswerIndex: null,
  156. currentNumberOfPoints: 1000,
  157. currentQuestion: parseInt(hashInfo[1], 10),
  158. nextNumberOfPoints: 200,
  159. voteRatios: null
  160. });
  161. }
  162. }
  163. // set navigation state
  164. navigate (route) {
  165. this.setState({ route });
  166. }
  167. // finish user session
  168. endUserSession () {
  169. api.endSession(this.state.userId, this.state.token);
  170. }
  171. // reset everything to restart the module
  172. reset () {
  173. this.setState({ route: 'titlescreen' });
  174. this.setup();
  175. }
  176. // LIFECYLCE
  177. componentWillMount () {
  178. this.setup();
  179. }
  180. // RENDER
  181. render () {
  182. let outputContent;
  183. // get header content
  184. const total = fixedDigits(config.numberQuestions, 2);
  185. const index = this.state.currentQuestion + 1; // 0-indexed
  186. const headerState = `${fixedDigits(index, 2)}/${total}`;
  187. switch (this.state.route) {
  188. case 'question':
  189. outputContent = <QuestionScreen
  190. {...content}
  191. isOffline={this.props.isOffline}
  192. route={this.state.route}
  193. mode={this.state.mode}
  194. headerState={headerState}
  195. currentQuestion={this.state.currentQuestion}
  196. currentAnswerIndex={this.state.currentAnswerIndex}
  197. currentAnswerCorrect={this.state.currentAnswerCorrect}
  198. currentNumberOfPoints={this.state.currentNumberOfPoints}
  199. currentAmount={this.state.currentAmount}
  200. nextNumberOfPoints={this.state.nextNumberOfPoints}
  201. toNextQuestion={this.toNextQuestion}
  202. voteRatios={this.state.voteRatios}
  203. setAnswer={this.setAnswer} />;
  204. break;
  205. case 'finalscreen':
  206. outputContent = <FinalScreen
  207. {...content}
  208. navigate={this.navigate}
  209. isFetching={this.state.isFetching} />;
  210. break;
  211. case 'titlescreen':
  212. default:
  213. outputContent = <TitleScreen
  214. {...content}
  215. navigate={this.navigate}
  216. navigateTo='question'
  217. isFetching={this.state.isFetching} />;
  218. break;
  219. }
  220. return outputContent;
  221. }
  222. }