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>