From 904e8029fea4436290fc79d5fabc782386cee19b Mon Sep 17 00:00:00 2001 From: "john.j.camilleri" Date: Fri, 7 Dec 2012 12:55:17 +0000 Subject: [PATCH] Syntax editor: various small improvements... - separate tree edit buttons from option buttons - fix bug when wrapping on freshly imported ast - add interface for import & export of ast - cleaner internal implementation of Editor.add_refinement - small style updates --- src/www/js/support.js | 7 ++ src/www/syntax-editor/README.md | 7 +- src/www/syntax-editor/ast.js | 5 +- src/www/syntax-editor/editor.css | 31 ++++++++- src/www/syntax-editor/editor.html | 2 +- src/www/syntax-editor/editor.js | 96 ++++++++++++++++++---------- src/www/syntax-editor/editor_menu.js | 75 ++++++++++++++-------- 7 files changed, 158 insertions(+), 65 deletions(-) diff --git a/src/www/js/support.js b/src/www/js/support.js index 44c0e3895..9913aa5e3 100644 --- a/src/www/js/support.js +++ b/src/www/js/support.js @@ -248,6 +248,13 @@ function insertAfter(el,ref) { ref.parentNode.insertBefore(el,ref.nextSibling); } +function toggleHidden(el) { + if (el.classList.contains("hidden")) + el.classList.remove("hidden") + else + el.classList.add("hidden") +} + /* --- Debug ---------------------------------------------------------------- */ function debug(s) { diff --git a/src/www/syntax-editor/README.md b/src/www/syntax-editor/README.md index ef9758e71..50f911f13 100644 --- a/src/www/syntax-editor/README.md +++ b/src/www/syntax-editor/README.md @@ -1,7 +1,7 @@ # GF web-based syntax editor John J. Camilleri -November 2012 +December 2012 An improved version of the [old syntax editor][1]. @@ -14,7 +14,7 @@ An improved version of the [old syntax editor][1]. ## Available startup options |Options|Description|Default| -|:------|:----------|:------| +|-------|-----------|-------| |target | |"editor"| |initial.grammar|Initial grammar URL, e.g. `"http://localhost:41296/grammars/Foods.pgf"`|-| |initial.startcat|Initial startcat|-| @@ -35,7 +35,7 @@ See `editor.html` and `editor_online.js`. ## TODO -- Wrap a subtree +- Import AST from text field - Compatibility with grammars with dependent category types - Clicking on tokens to select tree node - Clipboard of trees @@ -45,3 +45,4 @@ See `editor.html` and `editor_online.js`. - show all resulting linearizations/variants - undo/redo (or back/forward) navigation - structure fridge magnets more (eg newline before the magnet whose first letter is different) + diff --git a/src/www/syntax-editor/ast.js b/src/www/syntax-editor/ast.js index 89bdcf5c4..026452697 100644 --- a/src/www/syntax-editor/ast.js +++ b/src/www/syntax-editor/ast.js @@ -183,9 +183,11 @@ function AST(fun, cat) { var lid = Array.clone(id.get()); // clone NodeID array var node = this.root; - if (lid.length==1) + if (lid.length==1) { // Insert at root this.root = new ASTNode(subtree); + this.currentNode = this.root; + } else { lid.shift(); // throw away root while (lid.length>1 && node.hasChildren()) { @@ -297,6 +299,7 @@ function AST(fun, cat) { // (This probably needs a better home) AST.parse_type_signature = function(str) { var obj = { + signature: str, type: undefined, name: [], deps: [], diff --git a/src/www/syntax-editor/editor.css b/src/www/syntax-editor/editor.css index f36898d5c..6d4511eb0 100644 --- a/src/www/syntax-editor/editor.css +++ b/src/www/syntax-editor/editor.css @@ -14,6 +14,27 @@ body.syntax-editor { min-width: 5em; } +#import +{ + position: absolute; + display: inline-block; + padding: 0.3em; + background: #EEE; + border: 1px solid #999; + font-size: 0.95em; + box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); + } +#import.hidden +{ + display: none; + } +#import input[type=text] +{ + width: 20em; + font-family: monospace; + padding: 0.2em; + } + #tree { white-space:pre; @@ -41,6 +62,13 @@ body.syntax-editor { font-weight: bold; } +#refinements +{ + /* display: inline-block; */ + /* margin-left: 0.5em; */ + margin-top: 0.3em; + } + #linearisations { /* background: rgba(170, 170, 170, 0.5); */ @@ -65,13 +93,14 @@ body.syntax-editor { .refinement { - margin: 0 0.1em; + margin: 0.1em; display: inline-block; cursor: pointer; border: 1px solid; padding: 0.2em; font: 0.9em sans-serif; background: white; + box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); } .refinement.disabled { diff --git a/src/www/syntax-editor/editor.html b/src/www/syntax-editor/editor.html index 8105df60d..9464e0fa7 100644 --- a/src/www/syntax-editor/editor.html +++ b/src/www/syntax-editor/editor.html @@ -15,7 +15,7 @@
- John J. Camilleri, November 2012 + John J. Camilleri, December 2012 diff --git a/src/www/syntax-editor/editor.js b/src/www/syntax-editor/editor.js index 777766f55..65fe6c00f 100644 --- a/src/www/syntax-editor/editor.js +++ b/src/www/syntax-editor/editor.js @@ -29,15 +29,35 @@ function Editor(gm,opts) { this.container.classList.add("editor"); this.ui = { menubar: div_class("menu"), + tree: div_id("tree"), + + actionbar: div_id("actions"), + clear_button: button("Clear", function(){ + t.clear_node(); + }), + wrap_button: button("Wrap…", function(){ + t.wrap_candidates(); + }), + unwrap_button: button("Unwrap", function(){ + t.wrap_candidates(); + }), + refinements: div_id("refinements"), + lin: div_id("linearisations") }; appendChildren(this.container, [ - this.ui.menubar, - this.ui.tree, - this.ui.refinements, - this.ui.lin + t.ui.menubar, + t.ui.tree, + t.ui.actionbar, + t.ui.lin + ]); + appendChildren(this.ui.actionbar, [ + t.ui.clear_button, + t.ui.wrap_button, + // t.ui.unwrap_button, + t.ui.refinements ]); /* --- Client state initialisation -------------------------------------- */ @@ -142,25 +162,24 @@ Editor.prototype.start_fresh=function () { /* --- Functions for handling tree manipulation ----------------------------- */ -// Add refinement to UI, with a given callback function -// The opts param is used both in this function as: -// opts.disable_destructive -// as well as being passed to the callback function -Editor.prototype.add_refinement=function(t,fun,callback,opts) { - // var t = this; - if (!opts) opts = {}; +// Add refinement to UI, returning object +Editor.prototype.add_refinement=function(fun,opts) { + var t = this; + options = { + label: fun, + disable_destructive: true + }; + if (opts) for (var o in opts) options[o] = opts[o]; + var typeobj = t.lookup_fun(fun); // hide refinement if identical to current fun? - var opt = span_class("refinement", text(fun)); - opt.onclick = bind(function(){ - callback(opts); - }, opt); + var opt = span_class("refinement", text(options.label)); + opt.title = typeobj.signature; // If refinement would be destructive, disable it - if (opts.disable_destructive) { + if (options.disable_destructive) { var blank = t.ast.is_writable(); - var typeobj = t.lookup_fun(fun); var inplace = t.ast.fits_in_place(typeobj); if (!blank && !inplace) { opt.classList.add("disabled"); @@ -168,12 +187,13 @@ Editor.prototype.add_refinement=function(t,fun,callback,opts) { } t.ui.refinements.appendChild(opt); + return opt; } // Show refinements for given cat (usually that of current node) Editor.prototype.get_refinements=function(cat) { var t = this; - t.ui.refinements.innerHTML = "..."; + t.ui.refinements.innerHTML = "…"; if (cat == undefined) cat = t.ast.getCat(); var args = { @@ -182,12 +202,18 @@ Editor.prototype.get_refinements=function(cat) { }; var cont = function(data){ clear(t.ui.refinements); + // t.ui.refinements.innerHTML = "Refinements: "; + function addClickHandler(fun) { + return function() { + t.select_refinement.apply(t,[fun]); + } + } for (var pi in data.producers) { var fun = data.producers[pi]; - t.add_refinement(t, fun, bind(t.select_refinement,t), { - fun: fun, + var ref = t.add_refinement(fun, { disable_destructive: true }); + ref.onclick = addClickHandler(fun); } }; var err = function(data){ @@ -201,9 +227,8 @@ Editor.prototype.get_refinements=function(cat) { // Case 1: current node is blank/no kids // Case 2: kids have all same types, perform an in-place replacement // Case 3: kids have diff types/number, prevent replacement (must clear first) -Editor.prototype.select_refinement=function(opts) { +Editor.prototype.select_refinement=function(fun) { var t = this; - var fun = opts.fun; // Check if current node is blank or childless (case 1) var blank = t.ast.is_writable(); @@ -291,29 +316,30 @@ Editor.prototype.wrap_candidates = function() { t.ui.refinements.innerHTML = "Wrap with: "; for (var i in refinements) { var typeobj = refinements[i]; - // t.add_refinement(t, typeobj.name, bind(t.wrap,t), false); // Show a refinement for each potential child position + function addClickHandler(fun, child_id) { + return function() { + t.wrap.apply(t,[fun, child_id]); + } + } for (var a in typeobj.args) { var arg = typeobj.args[a]; if (arg == cat) { var label = typeobj.name + " ?" + (parseInt(a)+1); - t.add_refinement(t, label, bind(t.wrap,t), { - fun: typeobj.name, + var ref = t.add_refinement(typeobj.name, { + label: label, disable_destructive: false, - child_id: a }); + ref.onclick = addClickHandler(typeobj.name, a); } } } } // Wrap the current node inside another function -Editor.prototype.wrap = function(opts) { +Editor.prototype.wrap = function(fun, child_id) { var t = this; - var fun = opts.fun; - var child_id = opts.child_id; - var typeobj = t.grammar_constructors.funs[fun]; // do actual replacement @@ -348,6 +374,7 @@ Editor.prototype.generate_random = function() { server.get_random(args, cont, err); } +// Redraw tree Editor.prototype.redraw_tree=function() { var t = this; var elem = node; // function from support.js @@ -377,9 +404,11 @@ Editor.prototype.redraw_tree=function() { } } +// Get and display linearisations for AST Editor.prototype.update_linearisation=function(){ var t = this; - function langpart(conc,abs) { // langpart("FoodsEng","Foods") == "Eng" + // langpart("FoodsEng","Foods") == "Eng" + function langpart(conc,abs) { return hasPrefix(conc,abs) ? conc.substr(abs.length) : conc; } function row(lang, lin) { @@ -426,6 +455,9 @@ Editor.prototype.import_ast = function(abstr) { t.redraw_tree(); t.update_linearisation(); }; - server.pgf_call("abstrjson", args, cont); + var err = function(tree){ + alert("Invalid abstract syntax tree"); + }; + server.pgf_call("abstrjson", args, cont, err); } diff --git a/src/www/syntax-editor/editor_menu.js b/src/www/syntax-editor/editor_menu.js index 83375d68d..b5ceedd77 100644 --- a/src/www/syntax-editor/editor_menu.js +++ b/src/www/syntax-editor/editor_menu.js @@ -5,7 +5,13 @@ function EditorMenu(editor,opts) { // default values for options: this.options={ - target: "editor" + target: "editor", + show_grammar_menu: true, + show_startcat_menu: true, + show_to_menu: true, + show_random_button: true, + show_import: true, + show_export: true, } // Apply supplied options @@ -16,50 +22,53 @@ function EditorMenu(editor,opts) { this.ui = { grammar_menu: empty_id("select","grammar_menu"), startcat_menu: empty("select"), - to_toggle: button("Languages...", function(){ - var sel = t.ui.to_menu; - if (sel.classList.contains("hidden")) - sel.classList.remove("hidden") - else - sel.classList.add("hidden") + to_toggle: button("Languages…", function(){ + toggleHidden(t.ui.to_menu); }), to_menu: node("select",{ id: "to_menu", multiple: "multiple", class: "hidden" }), - wrap_button: button("Wrap", function(){ - t.editor.wrap_candidates(); - }), - clear_button: button("Clear", function(){ - t.editor.clear_node(); - }), random_button: button("Random", function(){ t.editor.generate_random(); }), - debug_toggle: button("⚙", function(){ - var sel = element("debug"); - if (sel.classList.contains("hidden")) - sel.classList.remove("hidden") - else - sel.classList.add("hidden") + import: { + toggle: button("Import…", function(){ + toggleHidden(t.ui.import.panel); + }), + panel: node("div",{ + id: "import", + class: "hidden" + }), + input: node("input",{type:"text"},[]), + button: button("Import", function(){ + t.editor.import_ast(t.ui.import.input.value); + toggleHidden(t.ui.import.panel); + }) + }, + export_button: button("Export", function(){ + alert(t.editor.ast.toString()); }), + debug_toggle: button("⚙", function(){ + toggleHidden(element("debug")); + }) }; - if (t.options.show.grammar_menu) { + if (t.options.show_grammar_menu) { appendChildren(t.container, [text(" Grammar: "), t.ui.grammar_menu]); t.ui.grammar_menu.onchange = function(){ var grammar_url = t.ui.grammar_menu.value; t.gm.change_grammar(grammar_url); } } - if (t.options.show.startcat_menu) { + if (t.options.show_startcat_menu) { appendChildren(t.container, [text(" Startcat: "), t.ui.startcat_menu]); t.ui.startcat_menu.onchange = function(){ var startcat = t.ui.startcat_menu.value; t.gm.change_startcat(startcat); } } - if (t.options.show.to_menu) { + if (t.options.show_to_menu) { appendChildren(t.container, [text(" To: "), t.ui.to_toggle, t.ui.to_menu]); t.ui.to_menu.onchange = function(){ var languages = new Array(); @@ -71,11 +80,25 @@ function EditorMenu(editor,opts) { t.gm.change_languages(languages); } } - appendChildren(t.container, [t.ui.clear_button]); - appendChildren(t.container, [t.ui.wrap_button]); - if (t.options.show.random_button) { + if (t.options.show_random_button) { appendChildren(t.container, [t.ui.random_button]); } + if (t.options.show_import) { + appendChildren(t.container, [ + t.ui.import.toggle, + t.ui.import.panel + ]); + appendChildren(t.ui.import.panel, [ + text("Import AST: "), + t.ui.import.input, + t.ui.import.button + ]); + } + if (t.options.show_export) { + appendChildren(t.container, [ + t.ui.export_button, + ]); + } appendChildren(t.container, [t.ui.debug_toggle]); /* --- Client state initialisation -------------------------------------- */ @@ -86,8 +109,6 @@ function EditorMenu(editor,opts) { /* --- Register Grammar Manager hooks ----------------------------------- */ this.gm.register_action("onload", bind(this.hook_onload, this)); this.gm.register_action("change_grammar", bind(this.hook_change_grammar, this)); - // this.gm.register_action("change_startcat", bind(this.hook_change_startcat, this)); - // this.gm.register_action("change_languages", bind(this.hook_change_languages, this)); }