plane.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. /**
  2. * Blockly Demos: Plane Seat Calculator
  3. *
  4. * Copyright 2012 Google Inc.
  5. * https://developers.google.com/blockly/
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. */
  19. /**
  20. * @fileoverview JavaScript for Blockly's Plane Seat Calculator demo.
  21. * @author fraser@google.com (Neil Fraser)
  22. */
  23. 'use strict';
  24. /**
  25. * Create a namespace for the application.
  26. */
  27. var Plane = {};
  28. /**
  29. * Lookup for names of supported languages. Keys should be in ISO 639 format.
  30. */
  31. Plane.LANGUAGE_NAME = {
  32. 'ar': 'العربية',
  33. 'be-tarask': 'Taraškievica',
  34. 'br': 'Brezhoneg',
  35. 'ca': 'Català',
  36. 'da': 'Dansk',
  37. 'de': 'Deutsch',
  38. 'el': 'Ελληνικά',
  39. 'en': 'English',
  40. 'es': 'Español',
  41. 'fa': 'فارسی',
  42. 'fr': 'Français',
  43. 'he': 'עברית',
  44. 'hrx': 'Hunsrik',
  45. 'hu': 'Magyar',
  46. 'ia': 'Interlingua',
  47. 'is': 'Íslenska',
  48. 'it': 'Italiano',
  49. 'ja': '日本語',
  50. 'ko': '한국어',
  51. 'ms': 'Bahasa Melayu',
  52. 'nb': 'Norsk Bokmål',
  53. 'nl': 'Nederlands, Vlaams',
  54. 'pl': 'Polski',
  55. 'pms': 'Piemontèis',
  56. 'pt-br': 'Português Brasileiro',
  57. 'ro': 'Română',
  58. 'ru': 'Русский',
  59. 'sc': 'Sardu',
  60. 'sv': 'Svenska',
  61. 'th': 'ภาษาไทย',
  62. 'tr': 'Türkçe',
  63. 'uk': 'Українська',
  64. 'vi': 'Tiếng Việt',
  65. 'zh-hans': '简体中文',
  66. 'zh-hant': '正體中文'
  67. };
  68. /**
  69. * List of RTL languages.
  70. */
  71. Plane.LANGUAGE_RTL = ['ar', 'fa', 'he'];
  72. /**
  73. * Main Blockly workspace.
  74. * @type {Blockly.WorkspaceSvg}
  75. */
  76. Plane.workspace = null;
  77. /**
  78. * Extracts a parameter from the URL.
  79. * If the parameter is absent default_value is returned.
  80. * @param {string} name The name of the parameter.
  81. * @param {string} defaultValue Value to return if paramater not found.
  82. * @return {string} The parameter value or the default value if not found.
  83. */
  84. Plane.getStringParamFromUrl = function(name, defaultValue) {
  85. var val = location.search.match(new RegExp('[?&]' + name + '=([^&]+)'));
  86. return val ? decodeURIComponent(val[1].replace(/\+/g, '%20')) : defaultValue;
  87. };
  88. /**
  89. * Extracts a numeric parameter from the URL.
  90. * If the parameter is absent or less than min_value, min_value is
  91. * returned. If it is greater than max_value, max_value is returned.
  92. * @param {string} name The name of the parameter.
  93. * @param {number} minValue The minimum legal value.
  94. * @param {number} maxValue The maximum legal value.
  95. * @return {number} A number in the range [min_value, max_value].
  96. */
  97. Plane.getNumberParamFromUrl = function(name, minValue, maxValue) {
  98. var val = Number(Plane.getStringParamFromUrl(name, 'NaN'));
  99. return isNaN(val) ? minValue : Math.min(Math.max(minValue, val), maxValue);
  100. };
  101. /**
  102. * Get the language of this user from the URL.
  103. * @return {string} User's language.
  104. */
  105. Plane.getLang = function() {
  106. var lang = Plane.getStringParamFromUrl('lang', '');
  107. if (Plane.LANGUAGE_NAME[lang] === undefined) {
  108. // Default to English.
  109. lang = 'en';
  110. }
  111. return lang;
  112. };
  113. /**
  114. * Is the current language (Plane.LANG) an RTL language?
  115. * @return {boolean} True if RTL, false if LTR.
  116. */
  117. Plane.isRtl = function() {
  118. return Plane.LANGUAGE_RTL.indexOf(Plane.LANG) != -1;
  119. };
  120. /**
  121. * Load blocks saved in session/local storage.
  122. * @param {string} defaultXml Text representation of default blocks.
  123. */
  124. Plane.loadBlocks = function(defaultXml) {
  125. try {
  126. var loadOnce = window.sessionStorage.loadOnceBlocks;
  127. } catch(e) {
  128. // Firefox sometimes throws a SecurityError when accessing sessionStorage.
  129. // Restarting Firefox fixes this, so it looks like a bug.
  130. var loadOnce = null;
  131. }
  132. if (loadOnce) {
  133. // Language switching stores the blocks during the reload.
  134. delete window.sessionStorage.loadOnceBlocks;
  135. var xml = Blockly.Xml.textToDom(loadOnce);
  136. Blockly.Xml.domToWorkspace(xml, Plane.workspace);
  137. } else if (defaultXml) {
  138. // Load the editor with default starting blocks.
  139. var xml = Blockly.Xml.textToDom(defaultXml);
  140. Blockly.Xml.domToWorkspace(xml, Plane.workspace);
  141. }
  142. Plane.workspace.clearUndo();
  143. };
  144. /**
  145. * Save the blocks and reload with a different language.
  146. */
  147. Plane.changeLanguage = function() {
  148. // Store the blocks for the duration of the reload.
  149. // This should be skipped for the index page, which has no blocks and does
  150. // not load Blockly.
  151. // MSIE 11 does not support sessionStorage on file:// URLs.
  152. if (typeof Blockly != 'undefined' && window.sessionStorage) {
  153. var xml = Blockly.Xml.workspaceToDom(Plane.workspace);
  154. var text = Blockly.Xml.domToText(xml);
  155. window.sessionStorage.loadOnceBlocks = text;
  156. }
  157. var languageMenu = document.getElementById('languageMenu');
  158. var newLang = encodeURIComponent(
  159. languageMenu.options[languageMenu.selectedIndex].value);
  160. var search = window.location.search;
  161. if (search.length <= 1) {
  162. search = '?lang=' + newLang;
  163. } else if (search.match(/[?&]lang=[^&]*/)) {
  164. search = search.replace(/([?&]lang=)[^&]*/, '$1' + newLang);
  165. } else {
  166. search = search.replace(/\?/, '?lang=' + newLang + '&');
  167. }
  168. window.location = window.location.protocol + '//' +
  169. window.location.host + window.location.pathname + search;
  170. };
  171. /**
  172. * Gets the message with the given key from the document.
  173. * @param {string} key The key of the document element.
  174. * @return {string} The textContent of the specified element,
  175. * or an error message if the element was not found.
  176. */
  177. Plane.getMsg = function(key) {
  178. var element = document.getElementById(key);
  179. if (element) {
  180. var text = element.textContent;
  181. // Convert newline sequences.
  182. text = text.replace(/\\n/g, '\n');
  183. return text;
  184. } else {
  185. return '[Unknown message: ' + key + ']';
  186. }
  187. };
  188. /**
  189. * User's language (e.g. "en").
  190. * @type {string}
  191. */
  192. Plane.LANG = Plane.getLang();
  193. Plane.MAX_LEVEL = 3;
  194. Plane.LEVEL = Plane.getNumberParamFromUrl('level', 1, Plane.MAX_LEVEL);
  195. Plane.rows1st = 0;
  196. Plane.rows2nd = 0;
  197. /**
  198. * Redraw the rows when the slider has moved.
  199. * @param {number} value New slider position.
  200. */
  201. Plane.sliderChange = function(value) {
  202. var newRows = Math.round(value * 410 / 20);
  203. Plane.redraw(newRows);
  204. };
  205. /**
  206. * Change the text of a label.
  207. * @param {string} id ID of element to change.
  208. * @param {string} text New text.
  209. */
  210. Plane.setText = function(id, text) {
  211. var el = document.getElementById(id);
  212. while (el.firstChild) {
  213. el.removeChild(el.firstChild);
  214. }
  215. el.appendChild(document.createTextNode(text));
  216. };
  217. /**
  218. * Display a checkmark or cross next to the answer.
  219. * @param {?boolean} ok True for checkmark, false for cross, null for nothing.
  220. */
  221. Plane.setCorrect = function(ok) {
  222. var yes = document.getElementById('seatYes');
  223. var no = document.getElementById('seatNo');
  224. yes.style.display = 'none';
  225. no.style.display = 'none';
  226. if (ok === true) {
  227. yes.style.display = 'block';
  228. } else if (ok === false) {
  229. no.style.display = 'block';
  230. }
  231. };
  232. /**
  233. * Initialize Blockly and the SVG plane.
  234. */
  235. Plane.init = function() {
  236. Plane.initLanguage();
  237. // Fixes viewport for small screens.
  238. var viewport = document.querySelector('meta[name="viewport"]');
  239. if (viewport && screen.availWidth < 725) {
  240. viewport.setAttribute('content',
  241. 'width=725, initial-scale=.35, user-scalable=no');
  242. }
  243. Plane.workspace = Blockly.inject('blockly',
  244. {media: '../../media/',
  245. rtl: Plane.isRtl(),
  246. toolbox: document.getElementById('toolbox')});
  247. var defaultXml =
  248. '<xml>' +
  249. ' <block type="plane_set_seats" deletable="false" x="70" y="70">' +
  250. ' </block>' +
  251. '</xml>';
  252. Plane.loadBlocks(defaultXml);
  253. Plane.workspace.addChangeListener(Plane.recalculate);
  254. Plane.workspace.addChangeListener(Blockly.Events.disableOrphans);
  255. // Initialize the slider.
  256. var svg = document.getElementById('plane');
  257. Plane.rowSlider = new Slider(60, 330, 425, svg, Plane.sliderChange);
  258. Plane.rowSlider.setValue(0.225);
  259. // Draw five 1st class rows.
  260. Plane.redraw(5);
  261. };
  262. /**
  263. * Initialize the page language.
  264. */
  265. Plane.initLanguage = function() {
  266. // Set the page title with the content of the H1 title.
  267. document.title += ' ' + document.getElementById('title').textContent;
  268. // Set the HTML's language and direction.
  269. // document.dir fails in Mozilla, use document.body.parentNode.dir instead.
  270. // https://bugzilla.mozilla.org/show_bug.cgi?id=151407
  271. var rtl = Plane.isRtl();
  272. document.head.parentElement.setAttribute('dir', rtl ? 'rtl' : 'ltr');
  273. document.head.parentElement.setAttribute('lang', Plane.LANG);
  274. // Sort languages alphabetically.
  275. var languages = [];
  276. for (var lang in Plane.LANGUAGE_NAME) {
  277. languages.push([Plane.LANGUAGE_NAME[lang], lang]);
  278. }
  279. var comp = function(a, b) {
  280. // Sort based on first argument ('English', 'Русский', '简体字', etc).
  281. if (a[0] > b[0]) return 1;
  282. if (a[0] < b[0]) return -1;
  283. return 0;
  284. };
  285. languages.sort(comp);
  286. // Populate the language selection menu.
  287. var languageMenu = document.getElementById('languageMenu');
  288. languageMenu.options.length = 0;
  289. for (var i = 0; i < languages.length; i++) {
  290. var tuple = languages[i];
  291. var lang = tuple[tuple.length - 1];
  292. var option = new Option(tuple[0], lang);
  293. if (lang == Plane.LANG) {
  294. option.selected = true;
  295. }
  296. languageMenu.options.add(option);
  297. }
  298. languageMenu.addEventListener('change', Plane.changeLanguage, true);
  299. };
  300. /**
  301. * Use the blocks to calculate the number of seats.
  302. * Display the calculated number.
  303. */
  304. Plane.recalculate = function() {
  305. // Find the 'set' block and use it as the formula root.
  306. var rootBlock = null;
  307. var blocks = Plane.workspace.getTopBlocks(false);
  308. for (var i = 0, block; block = blocks[i]; i++) {
  309. if (block.type == 'plane_set_seats') {
  310. rootBlock = block;
  311. }
  312. }
  313. var seats = NaN;
  314. Blockly.JavaScript.init(Plane.workspace);
  315. var code = Blockly.JavaScript.blockToCode(rootBlock);
  316. try {
  317. seats = eval(code);
  318. } catch (e) {
  319. // Allow seats to remain NaN.
  320. }
  321. Plane.setText('seatText',
  322. Plane.getMsg('Plane_seats').replace(
  323. '%1', isNaN(seats) ? '?' : seats));
  324. Plane.setCorrect(isNaN(seats) ? null : (Plane.answer() == seats));
  325. // Update blocks to show values.
  326. function updateBlocks(blocks) {
  327. for (var i = 0, block; block = blocks[i]; i++) {
  328. block.customUpdate && block.customUpdate();
  329. }
  330. }
  331. updateBlocks(Plane.workspace.getAllBlocks());
  332. updateBlocks(Plane.workspace.flyout_.workspace_.getAllBlocks());
  333. };
  334. /**
  335. * Calculate the correct answer.
  336. * @return {number} Number of seats.
  337. */
  338. Plane.answer = function() {
  339. if (Plane.LEVEL == 1) {
  340. return Plane.rows1st * 4;
  341. } else if (Plane.LEVEL == 2) {
  342. return 2 + (Plane.rows1st * 4);
  343. } else if (Plane.LEVEL == 3) {
  344. return 2 + (Plane.rows1st * 4) + (Plane.rows2nd * 5);
  345. }
  346. throw 'Unknown level.';
  347. };
  348. /**
  349. * Redraw the SVG to show a new number of rows.
  350. * @param {number} newRows
  351. */
  352. Plane.redraw = function(newRows) {
  353. var rows1st = Plane.rows1st;
  354. var rows2nd = Plane.rows2nd;
  355. var svg = document.getElementById('plane');
  356. if (newRows != rows1st) {
  357. while (newRows < rows1st) {
  358. var row = document.getElementById('row1st' + rows1st);
  359. row.parentNode.removeChild(row);
  360. rows1st--;
  361. }
  362. while (newRows > rows1st) {
  363. rows1st++;
  364. var row = document.createElementNS('http://www.w3.org/2000/svg', 'use');
  365. row.setAttribute('id', 'row1st' + rows1st);
  366. // Row of 4 seats.
  367. row.setAttribute('x', (rows1st - 1) * 20);
  368. row.setAttributeNS('http://www.w3.org/1999/xlink',
  369. 'xlink:href', '#row1st');
  370. svg.appendChild(row);
  371. }
  372. if (Plane.LEVEL == 3) {
  373. newRows = Math.floor((21 - newRows) * 1.11);
  374. while (newRows < rows2nd) {
  375. var row = document.getElementById('row2nd' + rows2nd);
  376. row.parentNode.removeChild(row);
  377. rows2nd--;
  378. }
  379. while (newRows > rows2nd) {
  380. rows2nd++;
  381. var row = document.createElementNS('http://www.w3.org/2000/svg',
  382. 'use');
  383. row.setAttribute('id', 'row2nd' + rows2nd);
  384. row.setAttribute('x', 400 - (rows2nd - 1) * 18);
  385. row.setAttributeNS('http://www.w3.org/1999/xlink',
  386. 'xlink:href', '#row2nd');
  387. svg.appendChild(row);
  388. }
  389. }
  390. if (Plane.LEVEL < 3) {
  391. Plane.setText('row1stText',
  392. Plane.getMsg('Plane_rows').replace('%1', rows1st));
  393. } else {
  394. Plane.setText('row1stText',
  395. Plane.getMsg('Plane_rows1').replace('%1', rows1st));
  396. Plane.setText('row2ndText',
  397. Plane.getMsg('Plane_rows2').replace('%1', rows2nd));
  398. }
  399. Plane.rows1st = rows1st;
  400. Plane.rows2nd = rows2nd;
  401. Plane.recalculate();
  402. }
  403. };
  404. window.addEventListener('load', Plane.init);
  405. // Load the user's language pack.
  406. document.write('<script src="generated/' + Plane.LANG + '.js"></script>\n');