Annotation of modules/damieng/graphical_editor/loncapa_daxe/web/loncapa_daxe.dart, revision 1.1
1.1 ! damieng 1: /*
! 2: This file is part of LON-CAPA.
! 3:
! 4: LON-CAPA is free software: you can redistribute it and/or modify
! 5: it under the terms of the GNU General Public License as published by
! 6: the Free Software Foundation, either version 3 of the License, or
! 7: (at your option) any later version.
! 8:
! 9: LON-CAPA is distributed in the hope that it will be useful,
! 10: but WITHOUT ANY WARRANTY; without even the implied warranty of
! 11: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
! 12: GNU General Public License for more details.
! 13:
! 14: You should have received a copy of the GNU General Public License
! 15: along with LON-CAPA. If not, see <http://www.gnu.org/licenses/>.
! 16: */
! 17:
! 18: library loncapa_daxe;
! 19:
! 20: import 'dart:async';
! 21: import 'dart:collection';
! 22: import 'dart:html' as h;
! 23: import 'package:daxe/daxe.dart';
! 24: import 'package:daxe/src/xmldom/xmldom.dart' as x;
! 25: import 'package:daxe/src/strings.dart';
! 26: import 'package:daxe/src/nodes/nodes.dart' show DNCData, DNText, SimpleTypeControl, ParentUpdatingDNText;
! 27: import 'dart:js' as js;
! 28:
! 29: import 'lcd_strings.dart';
! 30: part 'nodes/lcd_block.dart';
! 31: part 'nodes/lcd_parameter.dart';
! 32: part 'nodes/tex_mathjax.dart';
! 33: part 'nodes/lm.dart';
! 34: part 'nodes/radio_response.dart';
! 35: part 'nodes/radio_foilgroup.dart';
! 36: part 'nodes/radio_foil.dart';
! 37: part 'nodes/option_response.dart';
! 38: part 'nodes/option_foilgroup.dart';
! 39: part 'nodes/option_foil.dart';
! 40: part 'nodes/rank_response.dart';
! 41: part 'nodes/rank_foilgroup.dart';
! 42: part 'nodes/rank_foil.dart';
! 43: part 'nodes/hintgroup.dart';
! 44: part 'nodes/simple_ui_text.dart';
! 45: part 'nodes/script_block.dart';
! 46: part 'lcd_button.dart';
! 47:
! 48:
! 49: void main() {
! 50: NodeFactory.addCoreDisplayTypes();
! 51:
! 52: addDisplayType('lcdblock',
! 53: (x.Element ref) => new LCDBlock.fromRef(ref),
! 54: (x.Node node, DaxeNode parent) => new LCDBlock.fromNode(node, parent)
! 55: );
! 56:
! 57: addDisplayType('texmathjax',
! 58: (x.Element ref) => new TeXMathJax.fromRef(ref),
! 59: (x.Node node, DaxeNode parent) => new TeXMathJax.fromNode(node, parent)
! 60: );
! 61:
! 62: addDisplayType('lm',
! 63: (x.Element ref) => new Lm.fromRef(ref),
! 64: (x.Node node, DaxeNode parent) => new Lm.fromNode(node, parent)
! 65: );
! 66:
! 67: addDisplayType('script',
! 68: (x.Element ref) => new ScriptBlock.fromRef(ref),
! 69: (x.Node node, DaxeNode parent) => new ScriptBlock.fromNode(node, parent)
! 70: );
! 71:
! 72: addDisplayType('parameter',
! 73: (x.Element ref) => new LCDParameter.fromRef(ref),
! 74: (x.Node node, DaxeNode parent) => new LCDParameter.fromNode(node, parent)
! 75: );
! 76:
! 77: addDisplayType('radioresponse',
! 78: (x.Element ref) => new RadioResponse.fromRef(ref),
! 79: (x.Node node, DaxeNode parent) => new RadioResponse.fromNode(node, parent)
! 80: );
! 81:
! 82: addDisplayType('optionresponse',
! 83: (x.Element ref) => new OptionResponse.fromRef(ref),
! 84: (x.Node node, DaxeNode parent) => new OptionResponse.fromNode(node, parent)
! 85: );
! 86:
! 87: addDisplayType('rankresponse',
! 88: (x.Element ref) => new RankResponse.fromRef(ref),
! 89: (x.Node node, DaxeNode parent) => new RankResponse.fromNode(node, parent)
! 90: );
! 91:
! 92: addDisplayType('foilgroup',
! 93: (x.Element ref) {
! 94: if (ref.getAttribute('type') == 'radiobuttonresponse--foilgroup')
! 95: return new RadioFoilgroup.fromRef(ref);
! 96: else if (ref.getAttribute('type') == 'optionresponse--foilgroup')
! 97: return new OptionFoilgroup.fromRef(ref);
! 98: else if (ref.getAttribute('type') == 'rankresponse--foilgroup')
! 99: return new RankFoilgroup.fromRef(ref);
! 100: return new LCDBlock.fromRef(ref);
! 101: },
! 102: (x.Node node, DaxeNode parent) {
! 103: if (parent is RadioResponse)
! 104: return new RadioFoilgroup.fromNode(node, parent);
! 105: else if (parent is OptionResponse)
! 106: return new OptionFoilgroup.fromNode(node, parent);
! 107: else if (parent is RankResponse)
! 108: return new RankFoilgroup.fromNode(node, parent);
! 109: return new LCDBlock.fromNode(node, parent);
! 110: }
! 111: );
! 112:
! 113: addDisplayType('foil',
! 114: (x.Element ref) {
! 115: if (ref.getAttribute('type') == 'radiobuttonresponse--foil')
! 116: return new RadioFoil.fromRef(ref);
! 117: else if (ref.getAttribute('type') == 'optionresponse--foil')
! 118: return new OptionFoil.fromRef(ref);
! 119: else if (ref.getAttribute('type') == 'rankresponse--foil')
! 120: return new RankFoil.fromRef(ref);
! 121: return new LCDBlock.fromRef(ref);
! 122: },
! 123: (x.Node node, DaxeNode parent) {
! 124: if (parent is RadioFoilgroup)
! 125: return new RadioFoil.fromNode(node, parent);
! 126: else if (parent is OptionFoilgroup)
! 127: return new OptionFoil.fromNode(node, parent);
! 128: else if (parent is RankFoilgroup)
! 129: return new RankFoil.fromNode(node, parent);
! 130: return new LCDBlock.fromNode(node, parent);
! 131: }
! 132: );
! 133:
! 134: addDisplayType('hintgroup',
! 135: (x.Element ref) => new Hintgroup.fromRef(ref),
! 136: (x.Node node, DaxeNode parent) => new Hintgroup.fromNode(node, parent)
! 137: );
! 138:
! 139: Future.wait([Strings.load(), LCDStrings.load(), _readTemplates('templates.xml')]).then((List responses) {
! 140: _init_daxe().then((v) {
! 141: // add things to the toolbar
! 142: ToolbarMenu sectionMenu = _makeSectionMenu();
! 143: if (sectionMenu != null)
! 144: page.toolbar.add(sectionMenu);
! 145: x.Element texRef = doc.cfg.elementReference('m');
! 146: if (texRef != null) {
! 147: ToolbarBox insertBox = new ToolbarBox();
! 148: ToolbarButton texButton = new ToolbarButton(
! 149: LCDStrings.get('tex_equation'), 'images/tex.png',
! 150: () => doc.insertNewNode(texRef, 'element'), Toolbar.insertButtonUpdate,
! 151: data:new ToolbarStyleInfo([texRef], null, null));
! 152: insertBox.add(texButton);
! 153: page.toolbar.add(insertBox);
! 154: }
! 155: h.Element tbh = h.querySelector('.toolbar');
! 156: tbh.replaceWith(page.toolbar.html());
! 157: page.adjustPositionsUnderToolbar();
! 158: page.updateAfterPathChange();
! 159: // add things to the menubar
! 160: if (responses[2] is x.Document) {
! 161: // at this point the menubar html is already in the document, so we have to fix the HTML
! 162: h.Element menubarDiv = h.document.getElementsByClassName('menubar')[0];
! 163: if (doc.filePath.indexOf('&url=') != -1) { // otherwise we are not on LON-CAPA
! 164: MenuItem item = new MenuItem(Strings.get('menu.save'), () => save(), shortcut: 'S');
! 165: Menu fileMenu = page.mbar.menus[0];
! 166: fileMenu.add(item);
! 167: menubarDiv.firstChild.replaceWith(page.mbar.createMenuDiv(fileMenu));
! 168: }
! 169: Menu m = _makeTemplatesMenu(responses[2]);
! 170: page.mbar.add(m);
! 171: menubarDiv.append(page.mbar.createMenuDiv(m));
! 172: page.updateAfterPathChange();
! 173: } else
! 174: print("Error reading templates file, could not build the menu.");
! 175: });
! 176: });
! 177: }
! 178:
! 179: Future _init_daxe() {
! 180: Completer completer = new Completer();
! 181: doc = new DaxeDocument();
! 182: page = new WebPage();
! 183:
! 184: // check parameters for a config and file to open
! 185: String file = null;
! 186: String config = null;
! 187: String saveURL = null;
! 188: h.Location location = h.window.location;
! 189: String search = location.search;
! 190: if (search.startsWith('?'))
! 191: search = search.substring(1);
! 192: List<String> parameters = search.split('&');
! 193: for (String param in parameters) {
! 194: List<String> lparam = param.split('=');
! 195: if (lparam.length != 2)
! 196: continue;
! 197: if (lparam[0] == 'config')
! 198: config = lparam[1];
! 199: else if (lparam[0] == 'file')
! 200: file = Uri.decodeComponent(lparam[1]);
! 201: else if (lparam[0] == 'save')
! 202: saveURL = lparam[1];
! 203: }
! 204: if (saveURL != null)
! 205: doc.saveURL = saveURL;
! 206: if (config != null && file != null)
! 207: page.openDocument(file, config).then((v) => completer.complete());
! 208: else if (config != null)
! 209: page.newDocument(config).then((v) => completer.complete());
! 210: else {
! 211: h.window.alert(Strings.get('daxe.missing_config'));
! 212: completer.completeError(Strings.get('daxe.missing_config'));
! 213: }
! 214: return(completer.future);
! 215: }
! 216:
! 217: void save() {
! 218: saveOnLONCAPA().then((_) {
! 219: h.window.alert(Strings.get('save.success'));
! 220: }, onError: (DaxeException ex) {
! 221: h.window.alert(Strings.get('save.error') + ': ' + ex.message);
! 222: });
! 223: }
! 224:
! 225: /**
! 226: * Send the document with a POST request to LON-CAPA.
! 227: */
! 228: Future saveOnLONCAPA() {
! 229: int ind = doc.filePath.indexOf('&url=');
! 230: if (ind == -1)
! 231: return(new Future.error(new DaxeException('bad URL')));
! 232: String path = doc.filePath.substring(ind+5);
! 233: path = Uri.decodeQueryComponent(path);
! 234: ind = path.lastIndexOf('/');
! 235: String filename;
! 236: if (ind == -1)
! 237: filename = path;
! 238: else {
! 239: filename = path.substring(ind+1);
! 240: path = path.substring(0, ind+1);
! 241: }
! 242: Completer completer = new Completer();
! 243: String bound = 'AaB03x';
! 244: h.HttpRequest request = new h.HttpRequest();
! 245: request.onLoad.listen((h.ProgressEvent event) {
! 246: completer.complete(); // TODO: check for something, status is sometimes wrongly OK
! 247: });
! 248: request.onError.listen((h.ProgressEvent event) {
! 249: completer.completeError(new DaxeException(request.status.toString()));
! 250: });
! 251: request.open('POST', '/upload_file');
! 252: request.setRequestHeader('Content-Type', "multipart/form-data; boundary=$bound");
! 253:
! 254: StringBuffer sb = new StringBuffer();
! 255: sb.write("--$bound\r\n");
! 256: sb.write('Content-Disposition: form-data; name="uploads_path"\r\n');
! 257: sb.write('Content-type: text/plain; charset=UTF-8\r\n');
! 258: sb.write('Content-transfer-encoding: 8bit\r\n\r\n');
! 259: sb.write(path);
! 260: sb.write("\r\n--$bound\r\n");
! 261: sb.write('Content-Disposition: form-data; name="uploads"; filename="$filename"\r\n');
! 262: sb.write('Content-Type: application/octet-stream\r\n\r\n');
! 263: doc.dndoc.xmlEncoding = 'UTF-8'; // the document is forced to use UTF-8
! 264: sb.write(doc.toString());
! 265: sb.write('\r\n--$bound--\r\n\r\n');
! 266: request.send(sb.toString());
! 267: return(completer.future);
! 268: }
! 269:
! 270: ToolbarMenu _makeSectionMenu() {
! 271: Menu menu = new Menu(LCDStrings.get('Section'));
! 272: List<x.Element> sectionRefs = doc.cfg.elementReferences('section');
! 273: if (sectionRefs == null || sectionRefs.length == 0)
! 274: return(null);
! 275: x.Element h1Ref = doc.cfg.elementReference('h1');
! 276: for (String role in ['introduction', 'conclusion', 'prerequisites', 'objectives',
! 277: 'reminder', 'definition', 'demonstration', 'example', 'advise',
! 278: 'remark', 'warning', 'more_information', 'method',
! 279: 'activity', 'bibliography', 'citation']) {
! 280: MenuItem menuItem = new MenuItem(LCDStrings.get(role), null,
! 281: data:new ToolbarStyleInfo(sectionRefs, null, null));
! 282: menuItem.action = () {
! 283: ToolbarStyleInfo info = menuItem.data;
! 284: x.Element sectionRef = info.validRef;
! 285: LCDBlock section = NodeFactory.create(sectionRef);
! 286: section.state = 1;
! 287: section.setAttribute('class', 'role-' + role);
! 288: LCDBlock h1 = NodeFactory.create(h1Ref);
! 289: h1.state = 1;
! 290: if (doc.insert2(section, page.getSelectionStart())) {
! 291: doc.insertNode(h1, new Position(section, 0));
! 292: page.cursor.moveTo(new Position(h1, 0));
! 293: page.updateAfterPathChange();
! 294: }
! 295: };
! 296: menu.add(menuItem);
! 297: }
! 298: ToolbarMenu tbmenu = new ToolbarMenu(menu, Toolbar.insertMenuUpdate, page.toolbar);
! 299: return(tbmenu);
! 300: }
! 301:
! 302: Future<x.Document> _readTemplates(String templatesPath) {
! 303: x.DOMParser dp = new x.DOMParser();
! 304: return(dp.parseFromURL(templatesPath));
! 305: }
! 306:
! 307: Menu _makeTemplatesMenu(x.Document templatesDoc) {
! 308: Menu menu = new Menu(LCDStrings.get('Templates'));
! 309: x.Element templates = templatesDoc.documentElement;
! 310: for (x.Node child in templates.childNodes) {
! 311: if (child.nodeType == x.Node.ELEMENT_NODE && child.nodeName == 'menu') {
! 312: menu.add(_makeMenu(child));
! 313: }
! 314: }
! 315: return(menu);
! 316: }
! 317:
! 318: Menu _makeMenu(x.Element el) {
! 319: String locale = LCDStrings.systemLocale;
! 320: String defaultLocale = LCDStrings.defaultLocale;
! 321: String title;
! 322: for (x.Node child in el.childNodes) {
! 323: if (child.nodeType == x.Node.ELEMENT_NODE && child.nodeName == 'title') {
! 324: if (child.firstChild != null && child.firstChild.nodeType == x.Node.TEXT_NODE) {
! 325: if ((child as x.Element).getAttribute('lang') == locale) {
! 326: title = child.firstChild.nodeValue;
! 327: break;
! 328: } else if ((child as x.Element).getAttribute('lang') == defaultLocale) {
! 329: title = child.firstChild.nodeValue;
! 330: }
! 331: }
! 332: }
! 333: }
! 334: if (title == null)
! 335: title = '?';
! 336: Menu menu = new Menu(title);
! 337: for (x.Node child in el.childNodes) {
! 338: if (child.nodeType == x.Node.ELEMENT_NODE) {
! 339: if (child.nodeName == 'menu') {
! 340: menu.add(_makeMenu(child));
! 341: } else if (child.nodeName == 'item') {
! 342: menu.add(_makeItem(child));
! 343: }
! 344: }
! 345: }
! 346: return(menu);
! 347: }
! 348:
! 349: MenuItem _makeItem(x.Element item) {
! 350: String locale = LCDStrings.systemLocale;
! 351: String defaultLocale = LCDStrings.defaultLocale;
! 352: String path, type, title, help;
! 353: for (x.Node child in item.childNodes) {
! 354: if (child.nodeType == x.Node.ELEMENT_NODE) {
! 355: if (child.nodeName == 'title') {
! 356: if (child.firstChild != null && child.firstChild.nodeType == x.Node.TEXT_NODE) {
! 357: if ((child as x.Element).getAttribute('lang') == locale) {
! 358: title = child.firstChild.nodeValue;
! 359: } else if (title == null && (child as x.Element).getAttribute('lang') == defaultLocale) {
! 360: title = child.firstChild.nodeValue;
! 361: }
! 362: }
! 363: } else if (child.nodeName == 'path' && child.firstChild != null && child.firstChild.nodeType == x.Node.TEXT_NODE) {
! 364: path = child.firstChild.nodeValue;
! 365: } else if (child.nodeName == 'type' && child.firstChild != null && child.firstChild.nodeType == x.Node.TEXT_NODE) {
! 366: type = child.firstChild.nodeValue;
! 367: } else if (child.nodeName == 'help') {
! 368: if (child.firstChild != null && child.firstChild.nodeType == x.Node.TEXT_NODE) {
! 369: if ((child as x.Element).getAttribute('lang') == locale) {
! 370: help = child.firstChild.nodeValue;
! 371: } else if (help == null && (child as x.Element).getAttribute('lang') == defaultLocale) {
! 372: help = child.firstChild.nodeValue;
! 373: }
! 374: }
! 375: }
! 376: }
! 377: }
! 378: if (type == null) {
! 379: print("Warning: missing type for template $title\n");
! 380: type = 'problem';
! 381: }
! 382: x.Element refElement = doc.cfg.elementReference(type);
! 383: MenuItem menuItem = new MenuItem(title, () => _insertTemplate(path), data: refElement);
! 384: if (help != null)
! 385: menuItem.toolTipText = help;
! 386: return menuItem;
! 387: }
! 388:
! 389: void _insertTemplate(String filePath) {
! 390: try {
! 391: x.DOMParser dp = new x.DOMParser();
! 392: dp.parseFromURL(filePath).then((x.Document templateDoc) {
! 393: x.Element root = templateDoc.documentElement;
! 394: if (root == null)
! 395: return;
! 396: doc.removeWhitespace(root);
! 397: DaxeNode dnRoot = NodeFactory.createFromNode(root, null);
! 398: UndoableEdit edit;
! 399: Position pos = page.getSelectionStart();
! 400: if (dnRoot.nodeName == 'loncapa' && doc.getRootElement() != null)
! 401: edit = doc.insertChildrenEdit(dnRoot, pos, checkValidity:true);
! 402: else
! 403: edit = new UndoableEdit.insertNode(pos, dnRoot);
! 404: doc.doNewEdit(edit);
! 405: page.updateAfterPathChange();
! 406: });
! 407: } on x.DOMException catch(ex) {
! 408: h.window.alert(ex.toString());
! 409: }
! 410: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>