not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

841 lines
26 KiB

  1. define(["sugar-web/activity/activity", "webL10n", "sugar-web/datastore","sugar-web/graphics/colorpalette","zoompalette","sugar-web/graphics/presencepalette","humane","fontpalette","tutorial","sugar-web/env"], function (activity, webL10n, datastore, colorpalette, zoompalette, presencepalette, humane, textpalette, tutorial, env) {
  2. var defaultColor = '#FFF29F';
  3. var isShared = false;
  4. var isHost = false;
  5. var network = null;
  6. var connectedPeople = {};
  7. var xoLogo = '<?xml version="1.0" ?><!DOCTYPE svg PUBLIC \'-//W3C//DTD SVG 1.1//EN\' \'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\' [<!ENTITY stroke_color "#010101"><!ENTITY fill_color "#FFFFFF">]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="stock-xo_1_"><path d="M33.233,35.1l10.102,10.1c0.752,0.75,1.217,1.783,1.217,2.932 c0,2.287-1.855,4.143-4.146,4.143c-1.145,0-2.178-0.463-2.932-1.211L27.372,40.961l-10.1,10.1c-0.75,0.75-1.787,1.211-2.934,1.211 c-2.284,0-4.143-1.854-4.143-4.141c0-1.146,0.465-2.184,1.212-2.934l10.104-10.102L11.409,24.995 c-0.747-0.748-1.212-1.785-1.212-2.93c0-2.289,1.854-4.146,4.146-4.146c1.143,0,2.18,0.465,2.93,1.214l10.099,10.102l10.102-10.103 c0.754-0.749,1.787-1.214,2.934-1.214c2.289,0,4.146,1.856,4.146,4.145c0,1.146-0.467,2.18-1.217,2.932L33.233,35.1z" fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5"/><circle cx="27.371" cy="10.849" fill="&fill_color;" r="8.122" stroke="&stroke_color;" stroke-width="3.5"/></g></svg>';
  8. // Manipulate the DOM only when it is ready.
  9. requirejs(['domReady!'], function (doc) {
  10. // Initialize the activity.
  11. activity.setup();
  12. // Handle toolbar mode switch
  13. var currentMode = 0;
  14. var nodetextButton = document.getElementById("nodetext-button");
  15. var linkButton = document.getElementById("link-button");
  16. var removeButton = document.getElementById("delete-button");
  17. var switchMode = function(newMode) {
  18. currentMode = newMode;
  19. nodetextButton.classList.remove('active');
  20. linkButton.classList.remove('active');
  21. removeButton.classList.remove('active');
  22. if (newMode == 0) nodetextButton.classList.add('active');
  23. else if (newMode == 1) linkButton.classList.add('active');
  24. else if (newMode == 2) removeButton.classList.add('active');
  25. hideSubToolbar();
  26. if (lastSelected != null) {
  27. unselectAllNode();
  28. lastSelected = null;
  29. }
  30. }
  31. nodetextButton.addEventListener('click', function () { switchMode(0); }, true);
  32. linkButton.addEventListener('click', function () { switchMode(1); lastSelected = null; }, true);
  33. removeButton.addEventListener('click', function () { switchMode(2); }, true);
  34. var zoomButton = document.getElementById("zoom-button");
  35. zoomPalette = new zoompalette.zoomPalette(zoomButton);
  36. zoomPalette.addEventListener('pop', function(e) {
  37. hideSubToolbar();
  38. });
  39. zoomPalette.addEventListener('zoom', function(e) {
  40. var action = e.detail.zoom;
  41. var currentZoom = cy.zoom();
  42. var zoomStep = 0.25;
  43. if (action == 0) {
  44. if (currentZoom != cy.minZoom() && currentZoom-zoomStep > cy.minZoom()) cy.zoom(currentZoom-zoomStep);
  45. } else if (action == 1) {
  46. if (currentZoom != cy.maxZoom()) cy.zoom(currentZoom+zoomStep);
  47. } else if (action == 2) {
  48. cy.fit();
  49. } else if (action == 3) {
  50. cy.center();
  51. }
  52. });
  53. var pngButton = document.getElementById("png-button");
  54. pngButton.addEventListener('click', function(e) {
  55. var inputData = cy.png();
  56. var mimetype = inputData.split(";")[0].split(":")[1];
  57. var type = mimetype.split("/")[0];
  58. var metadata = {
  59. mimetype: mimetype,
  60. title: type.charAt(0).toUpperCase() + type.slice(1) + " LabyrinthJS",
  61. activity: "org.olpcfrance.MediaViewerActivity",
  62. timestamp: new Date().getTime(),
  63. creation_time: new Date().getTime(),
  64. file_size: 0
  65. };
  66. datastore.create(metadata, function() {
  67. console.log("export done.")
  68. }, inputData);
  69. });
  70. // Handle sub toolbar
  71. var subToolbar = document.getElementById("sub-toolbar");
  72. var textValue = document.getElementById("textvalue");
  73. var erasetextButton = document.getElementById("erasetext-button");
  74. var boldButton = document.getElementById("bold-button");
  75. var italicButton = document.getElementById("italics-button");
  76. var foregroundButton = document.getElementById("foreground-button");
  77. foregroundPalette = new colorpalette.ColorPalette(foregroundButton);
  78. foregroundPalette.setColor('rgb(0, 0, 0)');
  79. var ignoreForegroundEvent = false; // HACK: Need because SetColor itself raise an event
  80. var backgroundButton = document.getElementById("background-button");
  81. backgroundPalette = new colorpalette.ColorPalette(backgroundButton);
  82. backgroundPalette.setColor('rgb(255, 255, 255)');
  83. var ignoreBackgroundEvent = false;
  84. var fontMinusButton = document.getElementById("fontminus-button");
  85. var fontPlusButton = document.getElementById("fontplus-button");
  86. var fontButton = document.getElementById("font-button");
  87. fontPalette = new textpalette.TextPalette(fontButton);
  88. var helpButton = document.getElementById("help-button");
  89. helpButton.addEventListener('click', function(e) {
  90. tutorial.setElement("activity", document.getElementById("activity-button"));
  91. tutorial.setElement("stop", document.getElementById("stop-button"));
  92. tutorial.setElement("undo", document.getElementById("undo-button"));
  93. tutorial.setElement("redo", document.getElementById("redo-button"));
  94. tutorial.setElement("node", nodetextButton);
  95. tutorial.setElement("link", linkButton);
  96. tutorial.setElement("remove", removeButton);
  97. tutorial.setElement("png", pngButton);
  98. tutorial.setElement("zoom", zoomButton);
  99. tutorial.setElement("textvalue", textValue);
  100. tutorial.setElement("bold", boldButton);
  101. tutorial.setElement("italic", italicButton);
  102. tutorial.setElement("foreground", foregroundButton);
  103. tutorial.setElement("background", backgroundButton);
  104. tutorial.setElement("font", fontButton);
  105. tutorial.setElement("fontminus", fontMinusButton);
  106. tutorial.setElement("fontplus", fontPlusButton);
  107. tutorial.setElement("board", document.getElementById('canvas'));
  108. if (cy) {
  109. var selected = lastSelected;
  110. if (!selected) {
  111. var nodes = cy.elements("node");
  112. if (nodes.length > 0) {
  113. selected = nodes[0];
  114. lastSelected = selected;
  115. selectNode(selected);
  116. }
  117. }
  118. if (selected) {
  119. showSubToolbar(selected);
  120. }
  121. }
  122. tutorial.start();
  123. });
  124. foregroundPalette.addEventListener('colorChange', function(e) {
  125. if (!ignoreForegroundEvent) {
  126. lastSelected.style('color', e.detail.color);
  127. lastSelected.data('color', e.detail.color);
  128. pushState();
  129. }
  130. ignoreForegroundEvent = false;
  131. });
  132. backgroundPalette.addEventListener('colorChange', function(e) {
  133. if (!ignoreBackgroundEvent) {
  134. lastSelected.style('background-color', e.detail.color);
  135. lastSelected.data('background-color', e.detail.color);
  136. pushState();
  137. }
  138. ignoreBackgroundEvent = false;
  139. });
  140. fontPalette.addEventListener('fontChange', function(e) {
  141. var newfont = e.detail.family;
  142. lastSelected.data('font-family', newfont);
  143. lastSelected.removeClass('arial-text comic-text verdana-text');
  144. updateNodeText(lastSelected);
  145. if (newfont == 'Arial') lastSelected.addClass('arial-text');
  146. else if (newfont == 'Comic Sans MS') lastSelected.addClass('comic-text');
  147. else if (newfont == 'Verdana') lastSelected.addClass('verdana-text');
  148. pushState();
  149. });
  150. textValue.addEventListener('input', function() {
  151. if(textValue.value.length > 0)
  152. $("#erasetext-button").show();
  153. else
  154. $("#erasetext-button").hide();
  155. updateNodeText(lastSelected, textValue.value);
  156. pushState();
  157. });
  158. erasetextButton.addEventListener('click', function() {
  159. textValue.value = "";
  160. textValue.focus();
  161. updateNodeText(lastSelected, textValue.value);
  162. $("#erasetext-button").hide();
  163. pushState();
  164. });
  165. boldButton.addEventListener('click', function () {
  166. lastSelected.toggleClass('bold-text');
  167. if (lastSelected.hasClass('bold-text')) {
  168. boldButton.classList.add('active');
  169. } else {
  170. boldButton.classList.remove('active');
  171. }
  172. updateNodeText(lastSelected);
  173. pushState();
  174. });
  175. italicButton.addEventListener('click', function () {
  176. lastSelected.toggleClass('italic-text');
  177. if (lastSelected.hasClass('italic-text')) {
  178. italicButton.classList.add('active');
  179. } else {
  180. italicButton.classList.remove('active');
  181. }
  182. updateNodeText(lastSelected);
  183. pushState();
  184. });
  185. fontMinusButton.addEventListener('click', function() {
  186. lastSelected.data('font-size', Math.max(6, lastSelected.data('font-size')-2));
  187. updateNodeText(lastSelected);
  188. pushState();
  189. });
  190. fontPlusButton.addEventListener('click', function() {
  191. lastSelected.data('font-size', Math.min(100, lastSelected.data('font-size')+2));
  192. updateNodeText(lastSelected);
  193. pushState();
  194. });
  195. var showSubToolbar = function(node) {
  196. zoomPalette.popDown();
  197. subToolbar.style.visibility = "visible";
  198. textValue.value = node.style()["content"];
  199. if (node.hasClass('bold-text')) {
  200. boldButton.classList.add('active');
  201. } else {
  202. boldButton.classList.remove('active');
  203. }
  204. if (node.hasClass('italic-text')) {
  205. italicButton.classList.add('active');
  206. } else {
  207. italicButton.classList.remove('active');
  208. }
  209. ignoreForegroundEvent = true;
  210. foregroundPalette.setColor(node.style()['color']);
  211. ignoreBackgroundEvent = true;
  212. backgroundPalette.setColor(node.style()['background-color']);
  213. fontPalette.setFont(node.data('font-family'));
  214. }
  215. var hideSubToolbar = function() {
  216. subToolbar.style.visibility = "hidden";
  217. backgroundPalette.popDown();
  218. foregroundPalette.popDown();
  219. fontPalette.popDown();
  220. }
  221. // Handle graph save/world
  222. var stopButton = document.getElementById("stop-button");
  223. stopButton.addEventListener('click', function (event) {
  224. console.log("writing...");
  225. saveGraph(function (error) {
  226. if (error === null) {
  227. console.log("write done.");
  228. }
  229. else {
  230. console.log("write failed.");
  231. }
  232. });
  233. });
  234. // Handle localization
  235. window.addEventListener('localized', function() {
  236. env.getEnvironment(function(err, environment) {
  237. var defaultLanguage = (typeof chrome != 'undefined' && chrome.app && chrome.app.runtime) ? chrome.i18n.getUILanguage() : navigator.language;
  238. var language = environment.user ? environment.user.language : defaultLanguage;
  239. if (webL10n.language.code != language) {
  240. webL10n.language.code = language;
  241. };
  242. var oldDefaultText = defaultText;
  243. defaultText = webL10n.get("YourNewIdea");
  244. nodetextButton.title = webL10n.get("nodetextTitle");
  245. linkButton.title = webL10n.get("linkButtonTitle");
  246. removeButton.title = webL10n.get("removeButtonTitle");
  247. undoButton.title = webL10n.get("undoButtonTitle");
  248. redoButton.title = webL10n.get("redoButtonTitle");
  249. zoomButton.title = webL10n.get("zoomButtonTitle");
  250. foregroundButton.title = webL10n.get("foregroundButtonTitle");
  251. backgroundButton.title = webL10n.get("backgroundButtonTitle");
  252. textValue.placeholder = webL10n.get("typeText");
  253. boldButton.title = webL10n.get("boldButtonTitle");
  254. italicButton.title = webL10n.get("italicButtonTitle");
  255. fontMinusButton.title = webL10n.get("fontMinusButtonTitle");
  256. fontPlusButton.title = webL10n.get("fontPlusButtonTitle");
  257. fontButton.title = webL10n.get("fontButtonTitle");
  258. pngButton.title = webL10n.get("pngButtonTitle");
  259. if (cy) {
  260. var nodes = cy.elements("node");
  261. for(var i = 0; i < nodes.length ; i++) {
  262. var node = nodes[i];
  263. if (node.data('content') == oldDefaultText) {
  264. node.data('content', defaultText);
  265. node.style({'content': defaultText});
  266. }
  267. }
  268. }
  269. });
  270. }, false);
  271. // --- Cytoscape handling
  272. // Initialize board
  273. cy = cytoscape({
  274. container: document.getElementById('cy'),
  275. ready: function() {
  276. // Create first node and select id
  277. var firstNode = createNode(defaultText, getCenter());
  278. firstNode.select();
  279. selectNode(firstNode);
  280. lastSelected = firstNode;
  281. showSubToolbar(firstNode);
  282. pushState();
  283. // Load world
  284. loadGraph();
  285. },
  286. style: [
  287. {
  288. selector: '.standard-node',
  289. css: {
  290. 'text-valign': 'center',
  291. 'text-halign': 'center',
  292. 'border-color': 'darkgray',
  293. 'border-width': '1px',
  294. 'shape': 'roundrectangle'
  295. }
  296. },
  297. {
  298. selector: '.bold-text',
  299. css: {
  300. 'font-weight': 'bold'
  301. }
  302. },
  303. {
  304. selector: '.italic-text',
  305. css: {
  306. 'font-style': 'italic'
  307. }
  308. },
  309. {
  310. selector: '.arial-text',
  311. css: {
  312. 'font-family': 'Arial'
  313. }
  314. },
  315. {
  316. selector: '.comic-text',
  317. css: {
  318. 'font-family': 'Comic Sans MS'
  319. }
  320. },
  321. {
  322. selector: '.verdana-text',
  323. css: {
  324. 'font-family': 'Verdana'
  325. }
  326. }
  327. ]
  328. });
  329. // Event: a node is selected
  330. cy.on('tap', 'node', function() {
  331. if (currentMode == 2) {
  332. deleteNode(this);
  333. pushState();
  334. if (lastSelected == this) lastSelected = null;
  335. return;
  336. } else if (currentMode == 1) {
  337. if (lastSelected != null && lastSelected != this) {
  338. createEdge(lastSelected, this);
  339. pushState();
  340. }
  341. lastSelected = this;
  342. return;
  343. } else {
  344. if (isSelectedNode(this)) {
  345. unselectNode(this);
  346. hideSubToolbar();
  347. } else {
  348. selectNode(this);
  349. showSubToolbar(this);
  350. if (textValue.value == defaultText)
  351. textValue.setSelectionRange(0, textValue.value.length);
  352. else
  353. textValue.setSelectionRange(textValue.value.length, textValue.value.length);
  354. }
  355. lastSelected = this;
  356. }
  357. });
  358. // Event: a node is unselected
  359. cy.on('unselect', 'node', function() {
  360. unselectNode(this);
  361. });
  362. // Event: an edge is selected
  363. cy.on('select', 'edge', function() {
  364. if (currentMode == 2) {
  365. deleteEdge(this);
  366. pushState();
  367. return;
  368. }
  369. hideSubToolbar();
  370. });
  371. // Event: tap on the board
  372. cy.on('tap', function(e){
  373. if (e.cyTarget === cy) {
  374. if (currentMode == 0) {
  375. var newNode = createNode(defaultText, e.cyPosition);
  376. if (lastSelected != null) {
  377. createEdge(lastSelected, newNode);
  378. unselectNode(lastSelected);
  379. }
  380. pushState();
  381. newNode.select();
  382. selectNode(newNode);
  383. lastSelected = newNode;
  384. showSubToolbar(newNode);
  385. }
  386. }
  387. });
  388. // Event: elements moved
  389. cy.on('free', 'node', function(e) {
  390. pushState();
  391. });
  392. // --- Node and edge handling functions
  393. var nodeCount = 0;
  394. var edgeCount = 0;
  395. var defaultFontFamily = "Arial";
  396. var defaultFontSize = 16;
  397. var lastSelected = null;
  398. var defaultText = "<Your new idea>";
  399. // Create a new node with text and position
  400. var createNode = function(text, position) {
  401. var size = computeStringSize(text, defaultFontFamily, defaultFontSize, false, false);
  402. cy.add({
  403. group: 'nodes',
  404. nodes: [
  405. {
  406. data: {
  407. id: 'n'+(++nodeCount),
  408. 'font-family': defaultFontFamily,
  409. 'font-size': defaultFontSize,
  410. 'font-weight': 'normal',
  411. 'content': text,
  412. 'color': 'rgb(0, 0, 0)',
  413. 'background-color': 'rgb(255, 255, 255)'
  414. },
  415. position: {
  416. x: position.x,
  417. y: position.y
  418. }
  419. }
  420. ]
  421. });
  422. var newnode = cy.getElementById('n'+nodeCount);
  423. newnode.style({
  424. 'content': text,
  425. 'width': size.width,
  426. 'height': size.height,
  427. 'color': 'rgb(0, 0, 0)',
  428. 'font-family': 'Arial',
  429. 'font-size': defaultFontSize+'px',
  430. 'background-color': 'rgb(255, 255, 255)'
  431. });
  432. newnode.addClass('standard-node');
  433. return newnode;
  434. }
  435. // Update node text and change size
  436. var updateNodeText = function(node, text) {
  437. if (text === undefined) text = node.style()['content'];
  438. else node.data('content', text);
  439. var fontSize = node.data('font-size');
  440. var fontFamily = node.data('font-family');
  441. var size = computeStringSize(text, fontFamily, fontSize, node.hasClass('bold-text'), node.hasClass('italic-text'));
  442. node.style({
  443. 'content': text,
  444. 'font-size': fontSize+'px',
  445. 'font-family': fontFamily,
  446. 'width': size.width,
  447. 'height': size.height
  448. });
  449. }
  450. // Test if node is selected
  451. var isSelectedNode = function(node) {
  452. return node.style()['border-style'] == 'dashed';
  453. }
  454. // Set node as selected
  455. var selectNode = function(node) {
  456. node.style({
  457. 'border-color': 'black',
  458. 'border-style': 'dashed',
  459. 'border-width': '4px'
  460. });
  461. }
  462. // Set node as unselected
  463. var unselectNode = function(node) {
  464. node.style({
  465. 'border-color': 'darkgray',
  466. 'border-style': 'solid',
  467. 'border-width': '1px'
  468. });
  469. }
  470. // Unselect all node
  471. var unselectAllNode = function() {
  472. var nodes = cy.collection("node");
  473. for (var i = 0 ; i < nodes.length ; i++) {
  474. unselectNode(nodes[i]);
  475. }
  476. }
  477. // Delete node, linked edges are removed too
  478. var deleteNode = function(node) {
  479. cy.remove(node);
  480. }
  481. // Create a new edge between two nodes
  482. var createEdge = function(n1, n2) {
  483. cy.add({
  484. group: 'nodes',
  485. edges: [
  486. { data: { id: 'e'+(++edgeCount), source: n1.id(), target: n2.id() } }
  487. ]
  488. });
  489. }
  490. // Remove an edge
  491. var deleteEdge = function(edge) {
  492. cy.remove(edge);
  493. }
  494. // --- Utility functions
  495. // HACK: dynamically compute need size putting the string in a hidden div element
  496. var computeStringSize = function(text, fontfamily, fontsize, bold, italic) {
  497. var computer = document.getElementById("fontsizecomputer");
  498. computer.innerHTML = text.replace("<","&lt;").replace(">","&gt;");
  499. computer.style.fontFamily = fontfamily;
  500. computer.style.fontSize = fontsize+"px";
  501. if (bold) computer.style.fontWeight = "bold";
  502. else computer.style.fontWeight = "normal";
  503. if (italic) computer.style.fontStyle = "italic";
  504. else computer.style.fontStyle = "normal";
  505. return {width: (computer.clientWidth+fontsize)+"px", height: (computer.clientHeight+fontsize)+"px"};
  506. }
  507. // Get center of drawing zone
  508. var getCenter = function() {
  509. var canvas = document.getElementById("canvas");
  510. var center = {x: canvas.clientWidth/2, y: canvas.clientHeight/2};
  511. return center;
  512. }
  513. // Load graph from datastore
  514. var loadGraph = function() {
  515. var datastoreObject = activity.getDatastoreObject();
  516. datastoreObject.loadAsText(function (error, metadata, data) {
  517. if (data == null)
  518. return;
  519. displayGraph(JSON.parse(data));
  520. reinitState();
  521. pushState();
  522. });
  523. }
  524. // Save graph to datastore
  525. var saveGraph = function(callback) {
  526. var datastoreObject = activity.getDatastoreObject();
  527. var jsonData = getGraph();
  528. datastoreObject.setDataAsText(JSON.stringify(jsonData));
  529. datastoreObject.save(callback);
  530. }
  531. // Get a deep copy of current Graph
  532. var deepCopy = function(o) {
  533. var copy = o,k;
  534. if (o && typeof o === 'object') {
  535. copy = Object.prototype.toString.call(o) === '[object Array]' ? [] : {};
  536. for (k in o) {
  537. copy[k] = deepCopy(o[k]);
  538. }
  539. }
  540. return copy;
  541. }
  542. var getGraph = function() {
  543. return deepCopy(cy.json());
  544. }
  545. // Display a saved graph
  546. var displayGraph = function(graph) {
  547. // Destroy the graph
  548. cy.remove("node");
  549. cy.remove("edge");
  550. hideSubToolbar();
  551. lastSelected = null;
  552. // Recreate nodes and set styles and text
  553. cy.add({
  554. group: 'nodes',
  555. nodes: graph.elements.nodes
  556. });
  557. var nodes = cy.collection("node");
  558. var maxCount = 0;
  559. for (var i = 0 ; i < nodes.length ; i++) {
  560. var newnode = nodes[i];
  561. updateNodeText(newnode, newnode.data('content'));
  562. newnode.style('color', newnode.data('color'));
  563. newnode.style('background-color', newnode.data('background-color'));
  564. var id = newnode.data('id').substr(1);
  565. if (id > maxCount) maxCount = id;
  566. }
  567. nodeCount = maxCount+1;
  568. // Recreate edges
  569. maxCount = 0;
  570. if (graph.elements.edges) {
  571. cy.add({
  572. group: 'edges',
  573. edges: graph.elements.edges
  574. });
  575. var edges = cy.collection("edge");
  576. for (var i = 0 ; i < edges.length ; i++) {
  577. var id = edges[i].data('id').substr(1);
  578. if (id > maxCount) maxCount = id;
  579. }
  580. }
  581. edgeCount = maxCount+1;
  582. }
  583. // Do/Undo handling
  584. var stateHistory = [];
  585. var stateIndex = 0;
  586. var maxHistory = 30;
  587. var undoButton = document.getElementById("undo-button");
  588. undoButton.addEventListener('click', function () { undoState(); }, true);
  589. var redoButton = document.getElementById("redo-button");
  590. redoButton.addEventListener('click', function () { redoState(); }, true);
  591. var reinitState = function() {
  592. stateHistory = [];
  593. stateIndex = 0;
  594. }
  595. var pushState = function(fromNetwork) {
  596. if (stateIndex < stateHistory.length - 1) {
  597. var stateCopy = [];
  598. for (var i = 0 ; i < stateIndex + 1; i++)
  599. stateCopy.push(stateHistory[i]);
  600. stateHistory = stateCopy;
  601. }
  602. var stateLength = stateHistory.length - 1;
  603. var currentState = getGraph();
  604. if (stateLength < maxHistory) stateHistory.push(currentState);
  605. else {
  606. for (var i = 0 ; i < stateLength-1 ; i++) {
  607. stateHistory[i] = stateHistory[i+1];
  608. }
  609. stateHistory[stateLength-1] = currentState;
  610. }
  611. stateIndex = stateHistory.length - 1;
  612. if (isShared && !fromNetwork) {
  613. sendMessage({
  614. action: 'updateBoard',
  615. data: getGraph()
  616. });
  617. }
  618. updateStateButtons();
  619. }
  620. var undoState = function(fromNetwork) {
  621. if (stateHistory.length < 1 || (stateHistory.length >= 1 && stateIndex == 0)) return;
  622. displayGraph(stateHistory[--stateIndex]);
  623. if (isShared && !fromNetwork) {
  624. sendMessage({
  625. action: 'undoBoard'
  626. });
  627. }
  628. updateStateButtons();
  629. }
  630. var redoState = function(fromNetwork) {
  631. if (stateIndex+1 >= stateHistory.length) return;
  632. displayGraph(stateHistory[++stateIndex]);
  633. if (isShared && !fromNetwork) {
  634. sendMessage({
  635. action: 'redoBoard'
  636. });
  637. }
  638. updateStateButtons();
  639. }
  640. var updateStateButtons = function() {
  641. var stateLength = stateHistory.length;
  642. undoButton.disabled = (stateHistory.length < 1 || (stateHistory.length >= 1 && stateIndex == 0));
  643. redoButton.disabled = (stateIndex+1 >= stateLength);
  644. }
  645. // Users network list functions
  646. var generateXOLogoWithColor = function(color) {
  647. var coloredLogo = xoLogo;
  648. coloredLogo = coloredLogo.replace("#010101", color.stroke)
  649. coloredLogo = coloredLogo.replace("#FFFFFF", color.fill)
  650. return "data:image/svg+xml;base64," + btoa(coloredLogo);
  651. }
  652. var displayConnectedPeopleHtml = function() {
  653. var presenceUsersDiv = document.getElementById("presence-users");
  654. var html = "<hr><ul style='list-style: none; padding:0;'>"
  655. for (var key in connectedPeople) {
  656. html += "<li><img style='height:30px;' src='" + generateXOLogoWithColor(connectedPeople[key].colorvalue) + "'>" + connectedPeople[key].name + "</li>"
  657. }
  658. html += "</ul>"
  659. presenceUsersDiv.innerHTML = html
  660. }
  661. var displayConnectedPeople = function(users) {
  662. var presenceUsersDiv = document.getElementById("presence-users");
  663. if (!users || !presenceUsersDiv) {
  664. return;
  665. }
  666. network.listSharedActivityUsers(network.getSharedInfo().id, function(usersConnected) {
  667. connectedPeople = {};
  668. for (var i = 0; i < usersConnected.length; i++) {
  669. var userConnected = usersConnected[i];
  670. connectedPeople[userConnected.networkId] = userConnected;
  671. }
  672. displayConnectedPeopleHtml();
  673. });
  674. }
  675. // Handle activity sharing
  676. var networkButton = document.getElementById("network-button");
  677. var presence = new presencepalette.PresencePalette(networkButton, undefined);
  678. networkButton.addEventListener('click', function(e) {
  679. hideSubToolbar();
  680. });
  681. var shareActivity = function() {
  682. network = activity.getPresenceObject(function(error, network) {
  683. // Unable to join
  684. if (error) {
  685. console.log("error");
  686. return;
  687. }
  688. isShared = true;
  689. // Store settings
  690. userSettings = network.getUserInfo();
  691. console.log("connected");
  692. // Not found, create a new shared activity
  693. if (!window.top.sugar.environment.sharedId) {
  694. isHost = true;
  695. network.createSharedActivity('org.olpc-france.labyrinthjs', function(groupId) {});
  696. }
  697. // Show a disconnected message when the WebSocket is closed.
  698. network.onConnectionClosed(function(event) {
  699. console.log(event);
  700. console.log("Connection closed");
  701. });
  702. // Display connection changed
  703. network.onSharedActivityUserChanged(function(msg) {
  704. onNetworkUserChanged(msg);
  705. });
  706. // Handle messages received
  707. network.onDataReceived(onNetworkDataReceived);
  708. });
  709. }
  710. var sendMessage = function(content) {
  711. try {
  712. network.sendMessage(network.getSharedInfo().id, {
  713. user: network.getUserInfo(),
  714. content: content
  715. });
  716. } catch (e) {}
  717. }
  718. var onNetworkDataReceived = function(msg) {
  719. // Ignore messages coming from ourselves
  720. if (network.getUserInfo().networkId === msg.user.networkId) {
  721. return;
  722. }
  723. switch (msg.content.action) {
  724. case 'initialBoard':
  725. // Receive initial board from the host
  726. displayGraph(msg.content.data);
  727. break;
  728. case 'updateBoard':
  729. // Board change received
  730. displayGraph(msg.content.data);
  731. pushState(true);
  732. break;
  733. case 'undoBoard':
  734. // Undo board
  735. undoState(true);
  736. break;
  737. case 'redoBoard':
  738. // Redo board
  739. redoState(true);
  740. break;
  741. }
  742. }
  743. var onNetworkUserChanged = function(msg) {
  744. var userName = msg.user.name.replace('<', '&lt;').replace('>', '&gt;');
  745. var html = "<img style='height:30px;' src='" + generateXOLogoWithColor(msg.user.colorvalue) + "'>";
  746. if (msg.move === 1) {
  747. humane.log(html + webL10n.get("PlayerJoin",{user: userName}));
  748. if (isHost) {
  749. sendMessage({
  750. action: 'initialBoard',
  751. data: getGraph()
  752. });
  753. }
  754. } else if (msg.move === -1) {
  755. humane.log(html + webL10n.get("PlayerLeave",{user: userName}));
  756. }
  757. network.listSharedActivities(function(activities) {
  758. for (var i = 0; i < activities.length; i++) {
  759. if (activities[i].id === network.getSharedInfo().id) {
  760. displayConnectedPeople(activities[i].users);
  761. }
  762. }
  763. });
  764. }
  765. // Handle presence palette
  766. presence.addEventListener('shared', function() {
  767. presence.popDown();
  768. shareActivity();
  769. });
  770. if (window.top && window.top.sugar && window.top.sugar.environment && window.top.sugar.environment.sharedId) {
  771. console.log("ehy00");
  772. shareActivity();
  773. presence.setShared(true);
  774. }
  775. });
  776. });