diff options
| author | hallgren <hallgren@chalmers.se> | 2011-10-12 17:03:54 +0000 |
|---|---|---|
| committer | hallgren <hallgren@chalmers.se> | 2011-10-12 17:03:54 +0000 |
| commit | 44d1a5a9f71b03d9aceeccd760a63fcdc45f8bad (patch) | |
| tree | d51076a708997d6f1af6ac0deefd535bbc25f804 /src/www | |
| parent | 0aba45560d2033c37c3d2e876e6f3ef89e1554d6 (diff) | |
Improvements of "gf -server" mode and related setup
"gf -server" mode now contains everything needed to run the minibar and
the grammar editor (including example-based grammar writing).
The Setup.hs script installs the required files where gf -server can find them.
These files have been moved to a new directory: src/www.
The separate server program pgf-http is now obsolete.
Diffstat (limited to 'src/www')
47 files changed, 5137 insertions, 0 deletions
diff --git a/src/www/gfse/Makefile b/src/www/gfse/Makefile new file mode 100644 index 000000000..00a97b337 --- /dev/null +++ b/src/www/gfse/Makefile @@ -0,0 +1,7 @@ + +save: save.hs + ghc --make save.hs + +install:: + @make save + rsync -avz --exclude .DS_Store P *.html *.css *.js ../../runtime/javascript/minibar/support.js *.cgi *.manifest save www.grammaticalframework.org:/usr/local/www/GF/demos/gfse diff --git a/src/www/gfse/P/1306856253_weather_06.png b/src/www/gfse/P/1306856253_weather_06.png Binary files differnew file mode 100644 index 000000000..3f01afcaa --- /dev/null +++ b/src/www/gfse/P/1306856253_weather_06.png diff --git a/src/www/gfse/P/1307545089_weather_04.png b/src/www/gfse/P/1307545089_weather_04.png Binary files differnew file mode 100644 index 000000000..8a7f1e3ae --- /dev/null +++ b/src/www/gfse/P/1307545089_weather_04.png diff --git a/src/www/gfse/P/w1s.jpg b/src/www/gfse/P/w1s.jpg Binary files differnew file mode 100644 index 000000000..199fcb7ba --- /dev/null +++ b/src/www/gfse/P/w1s.jpg diff --git a/src/www/gfse/P/w2s.jpg b/src/www/gfse/P/w2s.jpg Binary files differnew file mode 100644 index 000000000..9673758c0 --- /dev/null +++ b/src/www/gfse/P/w2s.jpg diff --git a/src/www/gfse/P/w3s.jpg b/src/www/gfse/P/w3s.jpg Binary files differnew file mode 100644 index 000000000..1a8680ec7 --- /dev/null +++ b/src/www/gfse/P/w3s.jpg diff --git a/src/www/gfse/P/w4s.jpg b/src/www/gfse/P/w4s.jpg Binary files differnew file mode 100644 index 000000000..5c685c134 --- /dev/null +++ b/src/www/gfse/P/w4s.jpg diff --git a/src/www/gfse/TODO b/src/www/gfse/TODO new file mode 100644 index 000000000..22974ebe0 --- /dev/null +++ b/src/www/gfse/TODO @@ -0,0 +1,43 @@ ++ Safety question before deleting a grammar ++ Check identifier syntax ++ Allow lincat for deleted cat to be deleted ++ Allow lin for deleted fun to be deleted ++ Apply category alpha conversion in concrete syntax ++ Remove a concrete syntax ++ Apply function alpha conversion in concrete syntax ++ Change lhs of lins when function type is changed + ++ Allow languages other than the ones in the given list to be added ++ Export as plain text ++ Allow definitions to be reordered + ++ 1. possibility to compile the grammar set, returning a URL to a translator app ++ 2a. possibility to import modules - resource libraries +- 2b. possibility to import modules - user's own auxiliaries +- 3. possibility to upload own modules ++ 4. access to the created files in an on-line shell (making testing possible) +- 5. rule-to-rule type checking and guidance (e.g. with library oper + suggestions) ++ Try grammars in the Translation Quiz ++ Show lincat and lin before params and opers below ++ Create a new concrete syntax by copying an existing one. +- Easy access to compute_concrete from the editor +- Instead of starting with an empty grammar, start a new grammar by copying + an example. ++ Cloning grammars +- Allow grammars to contain a resoure module. Create the resource module by + factoring out common parts of the concrete syntaxes. +- Integrate example-based concrete syntax construction (using Ramona's tool) +- Open lexicon modules? They are not installed by default! + DictEng, MorphalouFre, DictSwe, DictTur, DictBul, DictFun, DictLav + ++ compile only the uploaded grammar even if other grammars are present ++ 'flags startcat' is needed for grammars with only one category (since the + default startcat is S, even if it doesn't exist) + +- Bug! After adding a 2nd def of a fun with a different type and then deleting + the old fun, the corresponding lin will have the wrong lhs. + ++ Bug! The startcat menu shows the first category by default, but the startcat + flag is actually not set until a selection is made from the menu. + diff --git a/src/www/gfse/about.html b/src/www/gfse/about.html new file mode 100644 index 000000000..8660621a3 --- /dev/null +++ b/src/www/gfse/about.html @@ -0,0 +1,243 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>About: GF online editor for simple multilingual grammars</title> +<link rel="stylesheet" type="text/css" href="editor.css" title="Cloud"> +<link rel="alternate stylesheet" type="text/css" href="molto.css" title="MOLTO"> + +<link rel=author href="http://www.cse.chalmers.se/~hallgren/" title="Thomas Hallgren"> + +<meta name = "viewport" content = "width = device-width"> +<meta http-equiv="Content-type" content="text/html;charset=UTF-8"> +<meta charset="UTF-8"> + +<script type="text/javascript" src="slideshow.js"></script> + +</head> + +<body> +<h1>GF online editor for simple multilingual grammars</h1> + +<div class=right> + <div class=slideshow> + <img onload="start_slideshow(this,{delay:5,fade:0.3})" src="P/w1s.jpg" alt="[GF online editor screen shoot]"> + <img class=hidden src="P/w2s.jpg" alt="[GF online editor screen shoot]"> + <img class=hidden src="P/w3s.jpg" alt="[GF online editor screen shoot]"> + <img class=hidden src="P/w4s.jpg" alt="[GF online editor screen shoot]"> + </div> +</div> +<h2>Introduction</h2> + +Traditionally, <a href="http://www.grammaticalframework.org/">GF</a> +grammars are created in a text editor and tested in the +GF shell. Text editors know very little (if anything) about the syntax of +GF grammars, and thus provide little guidance for novice GF users. Also, the +grammar author has to download and install the GF software on his/her own +computer. + +<p> +In contrast, the +<a href="."><em>GF online editor for simple multilingual grammars</em></a> +is available online, making it easier to get started. All that +is needed is a reasonably modern web browser. Even Android and iOS devices +can be used. +<p> +The editor +also guides the grammar author by showing a skeleton grammar file and +hinting how the parts should be filled in. When a new part is added to the +grammar, it is immediately checked for errors. + +<p> +Editing operations are accessed by clicking on editing symbols embedded +in the grammar display: +<span class=more>+</span>=Add an item, +<span class=delete>×</span>=Delete an item, +<span class=edit>%</span>=Edit an item. +These are revealed when hovering over items. On touch devices, hovering is +in some cases simulated by tapping, but there is also a button at the bottom +of the display to "Enable editing on touch devices" that reveals all editing +symbols. + +<p> +In spite of its name, the editor runs entierly in the web +browser, so once you have opened the web page, you can +<strong>continue editing</strong> grammars even while you are +<strong>offline</strong>. + +<h3>Current status</h3> + +<p> +At the moment, the editor supports only a small subset of the GF grammar +notation. +Proper error checking is done for abstract syntax, but not (yet) for concrete +syntax. + +<p> +The grammars created with this editor always consists of one file for the +abstract syntax, and one file for each concrete syntax. + +<h4>Abstract syntax</h4> + +The supported abstract syntax corresponds to context-free grammars +(no dependent types). The definition of an abstract syntax is limited to +<ul> + <li>a list of <em>category names</em>, + <var>Cat<sub>1</sub></var> ; ... ; <var>Cat<sub>n</sub></var>, + <li>a list of <em>functions</em> of the form + <var>Fun</var> : <var>Cat<sub>1</sub></var> -> ... -> + <var>Cat<sub>n</sub></var>, + <li>and a <em>start category</em>. +</ul> + +Available editing operations: +<ul> + <li>Categories can be added, removed and renamed. When renaming a category, + occurences of it in function types will be updated accordingly. + <li>Functions can be added, removed and edited. Concrete syntaxes are updated + to reflect changes. + <li>Functions can be reordered using drag-and-drop. +</ul> + +Error checks: + +<ul> + <li>Syntactically incorrect function definitions are refused. + <li>Semantic problem such as duplicated definitions or references to + undefined categories, are highlighted. +</ul> + +<h4>Concrete syntax</h4> + +At the moment, the concrete syntax for a language <var>L</var> is limited to +<ul> + <li>opening the Resource Grammar Library modules + <code>Syntax</code><var>L</var> and <code>Paradigms</code><var>L</var>, + <code>Lexicon</code><var>L</var> and <code>Extra</code><var>L</var>, + <li><em>linearization types</em> for the categories in the abstract syntax, + <li><em>linearizations</em> for the functions in the abstract syntax, + <li><em>parameter type definitions</em>, + <var>P</var> = <var>C<sub>1</sub></var> | ... |<var>C<sub>n</sub></var>, + <li>and <em>operation definitions</em>, <var>op</var> = <var>expr</var>, + <var>op</var> : <var>type</var> = <var>expr</var>, +</ul> + +Available editing operations: +<ul> + <li>The LHSs of the linearization types and linearizations are determined by + the abstract syntax and do not need to be entered manually. + The RHSs can + be edited. + <li>Parameter types can be added, removed and edited. + <li>Operation definitons can be added, removed and edited. + <li>Definitions can be reordered (using drag-and-drop) +</ul> +Also, + +<ul> + <li>When a new concrete syntax is added to the grammar, a copy of the + currently open concrete syntax is created, since copying and modifying + is usually easier than creating something new from scratch. + (If the abstract syntax is currently open, the new conrete syntax will + start out empty.) + <li>When adding a new concrete syntax, you normally pick one of the supported + languages from a list. The language code and the file name is determined + automatically. But you can also pick <em>Other</em> from the list and change + the language code afterwards to add a concrete syntax for a language + that is not in the list. +</ul> + +Error checks: +<ul> + <li>The RHSs in the concrete syntax are checked + for syntactic correctness by the editor as they are entered. + (TODO: the syntax of parameter types is not check at the moment.) + <li>Duplicated definitions are highlighted. Checks for other + semantic errors are delayed until the grammar is compiled. +</ul> + +<h3>Compiling and testing grammars</h3> + +When pressing the <strong>Compile</strong> button, the grammar will be compiled +with GF, and any errors not detected by the editor will be reported. +If the grammar is free from errors the user can then +test the grammar by clicking on links to the online GF shell, the Minibar or +the Translation Quiz. + +<h3><img class=right src="P/1307545089_weather_04.png" alt=""> +<img class=right src="P/1306856253_weather_06.png" alt="">Grammars in the +cloud</h3> + +While the editor normally stores grammars locally in the browser, it is also +possible to store grammars in the cloud. Grammars can be stored in the cloud +just for backup, or to make them accessible from multiple devices. + +<p> +There is no automatic synchronization between local grammars and the cloud. +Instead, the user should press +<img src="P/1306856253_weather_06.png" alt="[Cloud Upload]"> +to upload the grammars to the cloud, and press +<img src="P/1307545089_weather_04.png" alt="[Cloud download]"> +to download grammars from the cloud. In both cases, complete grammars +are copied and older versions at the destination will be overwritten. +When a grammar is deleted, both the local copy and the copy in the cloud +is deleted. + +<p> +Each device is initially assigned to its own unique cloud. Each device can thus +have its own set of grammars that are not available on other devices. It is +also possible to merge clouds and share a common set of grammars between +multiple devices: when uploading grammars to the cloud, a link to this grammar +cloud appears. Accessing this link from another device will cause the clouds of +the two devices to be merged. After this, grammars uploaded from one of the +devices can be downloaded on the other devices. Any number devices can join the +same grammar cloud in this way. + +<p> +<strong>Note</strong> that while it is possible to copy grammars between +multiple devices, there is no way to merge concurrent edits from multiple +devices. If the same grammar is uploaded to the +cloud from multiple devices, the last upload wins. Thus the current +implementation is suitable for a single user switching between different +devices, but not recommended for sharing grammars between multiple users. + +<p> +Also <strong>note</strong> that each grammar is assigned a unique identity +when it is first created. Renaming a grammar does not change its identity. +This means that name changes are propagated between devices like other changes. + +<h3>Example-based grammar writing</h3> + +This is work in progress... + +<h3>Future work</h3> + +This prototype gives an idea of how a web based GF grammar editor could work. +While this editor is implemented in JavaScript and runs in the web browser, +we do not expect to create a full implementation of GF that runs in the +web browser, but let the editor communicate with a server running GF. +<p> +By developing a GF server with an appropriate API, it should +be possible to extend the editor to support a larger fragment of GF, +to do proper error checking and make more of the existing GF shell functionality +accessible directly from the editor. +<p> +The current grammar cloud service is very primitive. In particular, it is not +suitable for multiple users developing a grammar in collaboration. + +<h3>Related documents</h3> +<ul> + <li><a href="http://www.grammaticalframework.org/~hallgren/Talks/GF/gf-ide.html">GF Grammar Development Tools</a>. Slides from a presentation at the MOLTO meeting in Göteborg, March 2011. + <li><a href="http://www.grammaticalframework.org/grammar-ide/">The GF Grammar IDE</a>. MOLTO deliverable. + <li><a href="http://www.grammaticalframework.org/compiler-api">The GF Grammar + Compiler API</a>. MOLTO deliverable. +</ul> + +<hr> +<div class=modtime><small> +<!-- hhmts start --> Last modified: Fri Oct 7 14:06:14 CEST 2011 <!-- hhmts end --> + </small></div> +<address> +<a href="http://www.cse.chalmers.se/~hallgren/">TH</a> +<img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt=""> +</address> +</body> </html> diff --git a/src/www/gfse/cloud.js b/src/www/gfse/cloud.js new file mode 100644 index 000000000..f19b6ee37 --- /dev/null +++ b/src/www/gfse/cloud.js @@ -0,0 +1,148 @@ + +function with_dir(cont) { + var dir=local.get("dir",""); + if(dir) cont(dir); + else ajax_http_get("upload.cgi?dir", + function(dir) { + local.put("dir",dir); + cont(dir); + }); +} + +function remove_cloud_grammar(g) { + var dir=local.get("dir") + if(dir && g.unique_name) { + var path=dir+"/"+g.unique_name+".json" + ajax_http_get("upload.cgi?rm="+encodeURIComponent(path),debug); + } +} + +// Upload the grammar to the server and check it for errors +function upload(g) { + function upload2(dir) { + var form=node("form",{method:"post",action:"upload.cgi"+dir}, + [hidden(g.basename+".gf",show_abstract(g))]) + for(var i in g.concretes) + form.appendChild(hidden(g.basename+g.concretes[i].langcode+".gf", + show_concrete(g.basename)(g.concretes[i]))); + editor.appendChild(form); + form.submit(); + form.parentNode.removeChild(form); + } + + with_dir(upload2); +} + +// Upload the grammar to store it in the cloud +function upload_json(cont) { + function upload3(resptext,status) { + local.put("json_uploaded",Date.now()); + //debug("Upload complete") + if(cont) cont(); + else { + var sharing=element("sharing"); + if(sharing) sharing.innerHTML=resptext; + } + } + function upload2(dir) { + var prefix=dir.substr(10)+"-" // skip "/tmp/gfse." + //debug("New form data"); + //var form=new FormData(); // !!! Doesn't work on Android 2.2! + var form="",sep=""; + //debug("Preparing form data"); + for(var i=0;i<local.count;i++) { + var g=local.get(i,null); + if(g) { + if(!g.unique_name) { + g.unique_name=prefix+i; + save_grammar(g) + } + //form.append(g.unique_name+".json",JSON.stringify(g)); + form+=sep+encodeURIComponent(g.unique_name+".json")+"="+ + encodeURIComponent(JSON.stringify(g)) + sep="&" + } + } + //debug("Upload to "+prefix); + ajax_http_post("upload.cgi"+dir,form,upload3,cont) + } + + with_dir(upload2); +} + +function download_json() { + var dir=local.get("dir"); + var index=grammar_index(); + var downloading=0; + + function get_list(ok,err) { + ajax_http_get("upload.cgi?ls="+dir,ok,err); + } + + function get_file(file,ok,err) { + downloading++; + ajax_http_get("upload.cgi?download="+encodeURIComponent(dir+"/"+file),ok,err); + } + + function file_failed(errormsg,status) { + debug(errormsg) + downloading--; + } + function file_downloaded(grammar) { + downloading--; + var newg=JSON.parse(grammar); + debug("Downloaded "+newg.unique_name) + var i=index[newg.unique_name]; + if(i!=undefined) merge_grammar(i,newg) + else { + debug("New") + newg.index=null; + save_grammar(newg); + } + if(downloading==0) done() + } + + function done() { + setTimeout(function(){location.href="."},2000); + } + + function download_files(ls) { + local.put("current",0); + if(ls) { + //debug("Downloading "+ls); + var files=ls.split(" "); + cleanup_deleted(files); + for(var i in files) get_file(files[i],file_downloaded,file_failed); + } + else { + debug("No grammars in the cloud") + done() + } + } + + get_list(download_files); +} + +function link_directories(newdir,cont) { + with_dir(function(olddir) { + ajax_http_get("upload.cgi?rmdir="+olddir+"&newdir="+newdir,cont) + }) +} + +/* -------------------------------------------------------------------------- */ + +// Send a command to the GF shell +function gfshell(cmd,cont) { + alert("gfshell(...) not implmemented!!!") +} + +// Check the syntax of an expression +function check_exp(s,cont) { + function check(gf_message) { + //debug("cc "+s+" = "+gf_message); + cont(/parse error/.test(gf_message) ? "parse error" : null); + } + if(navigator.onLine) + ajax_http_get("upload.cgi?cc="+encodeURIComponent(s),check) + else cont(null) +} diff --git a/src/www/gfse/cloud2.js b/src/www/gfse/cloud2.js new file mode 100644 index 000000000..e32749dc1 --- /dev/null +++ b/src/www/gfse/cloud2.js @@ -0,0 +1,159 @@ + +function with_dir(cont) { + var dir=local.get("dir",""); + if(/^\/tmp\//.test(dir)) cont(dir); + else ajax_http_get("/new", + function(dir) { + local.put("dir",dir); + cont(dir); + }); +} + +function remove_cloud_grammar(g) { + var dir=local.get("dir") + if(dir && g.unique_name) { + var path=g.unique_name+".json" + gfcloud("rm",{file:path},debug); + } +} + +// Upload the grammar to the server and check it for errors +function upload(g) { + function upload2(dir) { + var form=node("form",{method:"post",action:"/cloud"}, + [hidden("dir",dir),hidden("command","make"), + hidden(g.basename+".gf",show_abstract(g))]) + var files = [g.basename+".gf"] + for(var i in g.concretes) { + var cname=g.basename+g.concretes[i].langcode+".gf"; + files.push(cname); + form.appendChild(hidden(cname, + show_concrete(g.basename)(g.concretes[i]))); + } + editor.appendChild(form); + form.submit(); + form.parentNode.removeChild(form); + } + + function upload3(message) { if(message) alert(message); } + + with_dir(upload2) +} + +// Upload the grammar to store it in the cloud +function upload_json(cont) { + function upload3(resptext,status) { + local.put("json_uploaded",Date.now()); + //debug("Upload complete") + if(cont) cont(); + else { + var sharing=element("sharing"); + if(sharing) sharing.innerHTML=resptext; + } + } + function upload2(dir) { + var prefix=dir.substr(10)+"-" // skip "/tmp/gfse." + //debug("New form data"); + //var form=new FormData(); // !!! Doesn't work on Android 2.2! + var form={dir:dir}; + //debug("Preparing form data"); + for(var i=0;i<local.count;i++) { + var g=local.get(i,null); + if(g) { + if(!g.unique_name) { + g.unique_name=prefix+i; + save_grammar(g) + } + //form.append(g.unique_name+".json",JSON.stringify(g)); + form[encodeURIComponent(g.unique_name+".json")]=JSON.stringify(g) + } + } + //debug("Upload to "+prefix); + ajax_http_post("/cloud","command=upload"+encodeArgs(form),upload3,cont) + } + + with_dir(upload2); +} + +function download_json() { + var dir=local.get("dir"); + var index=grammar_index(); + var downloading=0; + + function get_list(ok,err) { gfcloud("ls",{},ok,err) } + + function get_file(file,ok,err) { + downloading++; + gfcloud("download",{file:encodeURIComponent(file)},ok,err); + } + + function file_failed(errormsg,status) { + debug(errormsg) + downloading--; + } + function file_downloaded(grammar) { + downloading--; + var newg=JSON.parse(grammar); + debug("Downloaded "+newg.unique_name) + var i=index[newg.unique_name]; + if(i!=undefined) merge_grammar(i,newg) + else { + debug("New") + newg.index=null; + save_grammar(newg); + } + if(downloading==0) done() + } + + function done() { + setTimeout(function(){location.href="."},2000); + } + + function download_files(ls) { + local.put("current",0); + if(ls) { + //debug("Downloading "+ls); + var files=ls.split(" "); + cleanup_deleted(files); + for(var i in files) get_file(files[i],file_downloaded,file_failed); + } + else { + debug("No grammars in the cloud") + done() + } + } + + get_list(download_files); +} + +function link_directories(newdir,cont) { + gfcloud("link_directories",{newdir:newdir},cont) +} + +/* -------------------------------------------------------------------------- */ + +// Request GF cloud service +function gfcloud(cmd,args,cont,err) { + with_dir(function(dir) { + var enc=encodeURIComponent; + var url="/cloud?dir="+enc(dir)+"&command="+enc(cmd)+encodeArgs(args) + ajax_http_get(url,cont,err) + }) +} + +// Send a command to the GF shell +function gfshell(cmd,cont) { + with_dir(function(dir) { + var enc=encodeURIComponent; + ajax_http_get("/gfshell?dir="+enc(dir)+"&command="+enc(cmd),cont) + }) +} + +// Check the syntax of an expression +function check_exp(s,cont) { + function check(gf_message) { + //debug("cc "+s+" = "+gf_message); + cont(/parse error/.test(gf_message) ? "parse error" : null); + } + gfshell("cc "+s,check); +} diff --git a/src/www/gfse/editor.css b/src/www/gfse/editor.css new file mode 100644 index 000000000..ef12fe8f5 --- /dev/null +++ b/src/www/gfse/editor.css @@ -0,0 +1,88 @@ +body { background: #eee; } +h1 { font-size: 175%; } +h1,h2,h3,h4,small { font-family: sans-serif; } +h1:first-child, h2:first-child { margin-top: 0; margin-bottom: 1ex; } + +#editor { max-width: 50em; } +div.home, div.grammar { border: 1px solid black; background: #9df; } +div.home { padding: 5px; } +div.files { margin: 0 8px 8px 8px; } + +div#file { border: 2px solid #009; border-top-width: 0; } +pre.plain { border: 2px solid #009; } +div#file, pre.plain { background: white; padding: 0.6ex; } + +.slideshow .hidden { display: none; } + +img.cloud, img.right, div.right, div.modtime { float: right; } +.modtime { color: #999; white-space: nowrap; } + +div.namebar { background: #9df; } +div.namebar table { width: 100%; } +.namebar h3, .home h3 { margin: 0; color: #009; } + +td.right { text-align: right; } + +.kw { font-weight: bold; font-family: sans-serif; color: #009; } +.sep { font-weight: bold; color: #009; } + +div.indent { padding-left: 1em; min-width: 1em; min-height: 1em; } + +div.fun:hover, div.param:hover, div.lincat:hover, div.oper:hover, div.lin:hover, +div.template:hover +{ background: #def;} + +.lin input[type=button], +.template input[type=button] { float: right; clear: right; margin: 0; } +.exb_output { background: #dfd; float: right; margin: 0 10px; } + +.more, .delete { font-weight: bold; font-family: sans-serif; } +.more, .delete, .edit { cursor: pointer; } + +.hover .more, .hover .delete, .hover .edit { visibility: hidden } + +.hover .hidden, .nohover .ifhover { display: none; } + +.editable:hover, .deletable:hover { background: #ff9; } + +.extensible:hover .more,.editable:hover > .edit ,.deletable:hover > .delete + { visibility: visible; } + +.more { color: green; } +.edit { color: orange; } +.delete { color: red; } +.error_message,.inError { color: red; } +.template, .template .sep { color: #999; } +form { display: inline-block; } + +table.tabs { + width: 100%; + border-width: 0; border-spacing: 0; empty-cells: show; +} + +table.tabs td { text-align: center; border: 2px solid #009; padding: 2px; } +table.tabs td.active { background: white; border-bottom-width: 0; } +table.tabs td.inactive { + background: #cef; + border-top-color: #66c; border-left-color: #66c; border-right-color: #66c; +} + + table.tabs td.gap +{ border-top-width: 0; border-left-width: 0; border-right-width: 0; } + +table.tabs input[type=button] { + border: 0; + background: inherit; + color: #009; + font-size: inherit; + font-weight: bold; + /*text-decoration: underline;*/ +} + +input.string_edit { font-family: inherit; font-size: inherit; } + +ul.languages { -moz-column-width: 20em; } + +li { margin-top: 0.5ex; margin-bottom: 0.5ex; } + +#sharing h1, #sharing .footer { display: none; }
\ No newline at end of file diff --git a/src/www/gfse/editor.js b/src/www/gfse/editor.js new file mode 100644 index 000000000..859f33a04 --- /dev/null +++ b/src/www/gfse/editor.js @@ -0,0 +1,1114 @@ + + +var editor=element("editor"); + +/* -------------------------------------------------------------------------- */ + +function initial_view() { + var current=local.get("current"); + if(current>0) open_grammar(current-1); + else draw_grammar_list(); + //debug(local.get("dir","no server directory yet")); +} + +function draw_grammar_list() { + local.put("current",0); + editor.innerHTML=""; + var uploaded=local.get("dir") && local.get("json_uploaded"); + var cloud_upload= + a(jsurl("upload_json()"), + [node("img",{"class":"cloud", + src:"P/1306856253_weather_06.png",alt:"[Up Cloud]", + title: uploaded + ? "Click to upload grammar updates to the cloud" + : "Click to store your grammars in the cloud"}, + [])]); + var home=div_class("home",[node("h3",{}, + [text("Your grammars"),cloud_upload])]); + if(uploaded) { + var cloud_download= + a(jsurl("download_json()"), + [node("img",{"class":"cloud", + src:"P/1307545089_weather_04.png",alt:"[Down Cloud]", + title:"Click to download grammar updates from the cloud"}, + [])]); + insertAfter(cloud_download,cloud_upload); + } + editor.appendChild(home) + var gs=ul([]); + function del(i) { return function () { delete_grammar(i); } } + function clone(i) { return function (g,b) { clone_grammar(i); } } + for(var i=0;i<local.count;i++) { + var grammar=local.get(i,null); + if(grammar && grammar.basename) { + var link=a(jsurl("open_grammar("+i+")"),[text(grammar.basename)]); + gs.appendChild( + node("li",{"class":"extensible"}, + [deletable(del(i),link,"Delete this grammar"), + more(grammar,clone(i),"Clone this grammar")])) + } + } + if(local.get("count",null)==null) + home.appendChild(text("You have not created any grammars yet.")); + else if(local.count==0) + home.appendChild(text("Your grammar list is empty.")); + home.appendChild(gs); + + home.appendChild( + ul([li([a(jsurl("new_grammar()"),[text("New grammar")])])])); + //editor.appendChild(text(local.count)); + home.appendChild(empty_id("div","sharing")); +} + +function new_grammar() { + var g={basename:"Unnamed", + abstract:{cats:[],funs:[]}, + concretes:[]} + edit_grammar(g); +} + +function remove_local_grammar(i) { + local.remove(i); + while(local.count>0 && !local.get(local.count-1)) + local.count--; +} + +function delete_grammar(i) { + var g=local.get(i); + var ok=confirm("Do you really want to delete the grammar "+g.basename+"?") + if(ok) { + remove_local_grammar(i) + remove_cloud_grammar(g) + initial_view(); + } +} + +function clone_grammar(i) { + var old=local.get(i); + var g={basename:old.basename,abstract:old.abstract,concretes:old.concretes} + save_grammar(g); + draw_grammar_list(); +} + +function open_grammar(i) { + var g=local.get(i); + g.index=i; + local.put("current",i+1); + edit_grammar(g); +} + +function close_grammar(g) { save_grammar(g); draw_grammar_list(); } +function reload_grammar(g) { save_grammar(g); edit_grammar(g); } + +function save_grammar(g) { + if(g.index==null) g.index=local.count++; + local.put(g.index,g); +} + +function edit_grammar(g) { + editor.innerHTML=""; + editor.appendChild(draw_grammar(g)); +} + + +function draw_grammar(g) { + var files=div_class("files",[draw_filebar(g),draw_file(g)]); + return div_class("grammar",[draw_namebar(g,files),files]) + +} + +function draw_namebar(g,files) { + return div_class("namebar", + [table([tr([td(draw_name(g)), + td_right([draw_plainbutton(g,files), + upload_button(g), + draw_closebutton(g)])])])]) +} + +function draw_name(g) { + return editable("h3",text(g.basename),g,edit_name,"Rename grammar"); +} + +function draw_closebutton(g) { + var b=button("X",function(){close_grammar(g);}); + b.title="Save and Close this grammar"; + return b; +} + +function draw_plainbutton(g,files) { + var b2; + function show_editor() { edit_grammar(g); } + function show_plain() { + files.innerHTML="<pre class=plain>"+show_grammar(g)+"</pre>" + b.style.display="none"; + if(b2) b2.style.display=""; + else { + b2=button("Show editor",show_editor); + insertAfter(b2,b); + } + } + var b=button("Show plain",show_plain); + b.title="Show plain text representaiton of the grammar"; + return b; +} + +function upload_button(g) { + var b=button("Compile",function(){upload(g);}); + b.title="Upload the grammar to the server to check it in GF and test it in the minibar"; + return b; +} + +function lang(code,name) { return { code:code, name:name} } +function lang1(name) { + var ws=name.split("/"); + return ws.length==1 ? lang(name.substr(0,3),name) : lang(ws[0],ws[1]); +} +var languages = + map(lang1,"Amharic Arabic Bulgarian Catalan Danish Dutch English Finnish French German Hindi Ina/Interlingua Italian Latin Norwegian Polish Ron/Romanian Russian Spanish Swedish Thai Turkish Urdu".split(" ")); +languages.push(lang("Other","Other")); + +var langname={}; +for(var i in languages) + langname[languages[i].code]=languages[i].name + +function concname(code) { return langname[code] || code; } + +function add_concrete(g,el) { + var file=element("file"); + file.innerHTML=""; + var dc={}; + for(var i in g.concretes) + dc[g.concretes[i].langcode]=true; + var list=[] + for(var i in languages) { + var l=languages[i], c=l.code; + if(!dc[c]) + list.push(li([a(jsurl("add_concrete2("+g.index+",'"+c+"')"), + [text(l.name)])])); + } + var from= g.current>0 + ? "a copy of "+concname(g.concretes[g.current-1].langcode) + :"scratch"; + file.appendChild(p(text("You are about to create a new concrete syntax by starting from "+from+"."))); + file.appendChild(p(text("Pick a language for the new concrete syntax:"))); + file.appendChild(node("ul",{"class":"languages"},list)); +} + +function new_concrete(code) { + return { langcode:code,params:[],lincats:[],opers:[],lins:[] }; +} + +function adjust_opens(cnc,oldcode,code) { + for(var oi in cnc.opens) + for(var li in rgl_modules) + if(cnc.opens[oi]==rgl_modules[li]+oldcode) + cnc.opens[oi]=rgl_modules[li]+code; +} + +function add_concrete2(ix,code) { + var g=local.get(ix); + var cs=g.concretes; + var ci; + for(var ci=0;ci<cs.length;ci++) if(cs[ci].langcode==code) break; + if(ci==cs.length) { + if(g.current>0) { + cs.push(cs[g.current-1]); // old and new are shared at this point + save_grammar(g); // serialization loses sharing + g=local.get(ix); // old and new are separate now + var oldcode=cs[g.current-1].langcode; + var cnc=g.concretes[ci]; + cnc.langcode=code; + adjust_opens(cnc,oldcode,code); + timestamp(cnc) + } + else + cs.push(new_concrete(code)) + save_grammar(g); + } + open_concrete(g,ci); +} + +function open_abstract(g) { g.current=0; reload_grammar(g); } +function open_concrete(g,i) { g.current=i+1; reload_grammar(g); } + +function td_gap(c) {return wrap_class("td","gap",c); } +function gap() { return td_gap(text(" ")); } + +function tab(active,link) { + return wrap_class("td",active ? "active" : "inactive",link); +} + +function delete_concrete(g,ci) { + var c=g.concretes[ci]; + var ok=c.params.length==0 && c.lincats.length==0 && c.opers.length==0 + && c.lins.length==0 + || confirm("Do you really want to delete the concrete syntax for "+ + concname(c.langcode)+"?"); + if(ok) { + g.concretes=delete_ix(g.concretes,ci) + if(g.current && g.current-1>=ci) g.current--; + reload_grammar(g); + } +} + +function draw_filebar(g) { + var cur=(g.current||0)-1; + var filebar = empty_class("tr","extensible") + filebar.appendChild(gap()); + filebar.appendChild( + tab(cur== -1,button("Abstract",function(){open_abstract(g);}))); + var cs=g.concretes; + function del(ci) { return function() { delete_concrete(g,ci); }} + function open_conc(i) { return function() {open_concrete(g,1*i); }} + for(var i in cs) { + filebar.appendChild(gap()); + filebar.appendChild( + tab(i==cur,deletable(del(i),button(concname(cs[i].langcode),open_conc(i)),"Delete this concrete syntax"))); + } + filebar.appendChild(td_gap(more(g,add_concrete,"Add a concrete syntax"))); + return wrap_class("table","tabs",filebar); +} + +function draw_file(g) { + return g.current>0 // && g.current<=g.concretes.length + ? draw_concrete(g,g.current-1) + : draw_abstract(g); +} + +function draw_startcat(g) { + var abs=g.abstract; + var startcat = abs.startcat || abs.cats[0]; + function opt(cat) { return option(cat,cat); } + var m= node("select",{},map(opt,abs.cats)); + m.value=startcat; + m.onchange=function() { + if(m.value!=abs.startcat) { + abs.startcat=m.value; + timestamp(abs); + save_grammar(g); + } + } + return indent([kw("flags startcat"),sep(" = "),m]); +} + +function draw_abstract(g) { + var kw_cat = kw("cat"); + kw_cat.title = "The categories (nonterminals) of the grammar are enumerated here. [C.3.2]"; + var kw_fun = kw("fun"); + kw_fun.title = "The functions (productions) of the grammar are enumerated here. [C.3.4]"; + var flags=g.abstract.startcat || g.abstract.cats.length>1 + ? draw_startcat(g) + : text(""); + function sort_funs() { + g.abstract.funs=sort_list(this,g.abstract.funs,"name"); + timestamp(g.abstract); + save_grammar(g); + } + return div_id("file", + [kw("abstract "),ident(g.basename),sep(" = "), + draw_timestamp(g.abstract), + flags, + indent([extensible([kw_cat, + indent(draw_cats(g))]), + extensible([kw_fun, + indent_sortable(draw_funs(g),sort_funs)])])]); +} + +function add_cat(g,el) { + function add(s) { + var cats=s.split(/\s*(?:\s|[;])\s*/); // allow separating spaces or ";" + if(cats.length>0 && cats[cats.length-1]=="") cats.pop(); + for(var i in cats) { + var err=check_name(cats[i],"Category"); + if(err) return err; + } + for(var i in cats) g.abstract.cats.push(cats[i]); + timestamp(g.abstract); + reload_grammar(g); + return null; + } + string_editor(el,"",add); +} + +function delete_cat(g,ix) { + with(g.abstract) cats=delete_ix(cats,ix); + timestamp(g.abstract); + reload_grammar(g); +} + +function rename_cat(g,el,cat) { + function ren(newcat) { + if(newcat!="" && newcat!=cat) { + var err=check_name(newcat,"Category"); + if(err) return err; + var dc=defined_cats(g); + if(dc[newcat]) return newcat+" is already in use"; + g=rename_category(g,cat,newcat); + timestamp(g.abstract); + reload_grammar(g); + } + return null; + } + string_editor(el,cat,ren); +} + +function draw_cats(g) { + var cs=g.abstract.cats; + var es=[]; + var defined={}; + function eident(cat) { + function ren(g,el) { rename_cat(g,el,cat); } + return editable("span",ident(cat),g,ren,"Rename category"); + } + function check(cat,el) { + return ifError(defined[cat],"Same category named twice",el); + } + function del(i) { return function() { delete_cat(g,i); }} + for(var i in cs) { + es.push(deletable(del(i),check(cs[i],eident(cs[i])),"Delete this category")); + defined[cs[i]]=true; + es.push(sep("; ")); + } + es.push(more(g,add_cat,"Add more categories")); + return es; +} + +function add_fun(g,el) { + function add(s) { + var p=parse_fun(s); + if(p.ok) { + g.abstract.funs.push(p.ok); + timestamp(g.abstract); + reload_grammar(g); + return null; + } + else + return p.error + } + string_editor(el,"",add); +} + +function edit_fun(i) { + return function (g,el) { + function replace(s) { + var p=parse_fun(s); + if(p.ok) { + var old=g.abstract.funs[i]; + g.abstract.funs[i]=p.ok; + if(p.ok.name!=old.name) g=rename_function(g,old.name,p.ok.name); + if(show_type(p.ok.type)!=show_type(old.type)) + g=change_lin_lhs(g,p.ok); + timestamp(g.abstract); + reload_grammar(g); + return null; + } + else + return p.error; + } + string_editor(el,show_fun(g.abstract.funs[i]),replace); + } +} + +function delete_fun(g,ix) { + with(g.abstract) funs=delete_ix(funs,ix); + timestamp(g.abstract); + reload_grammar(g); +} + +function draw_funs(g) { + var funs=g.abstract.funs; + var es=[]; + var dc=defined_cats(g); + var df={}; + function del(i) { return function() { delete_fun(g,i); }} + function draw_efun(i,df) { + return editable("span",draw_fun(funs[i],dc,df),g,edit_fun(i),"Edit this function"); + } + for(var i in funs) { + es.push(node_sortable("fun",funs[i].name,[deletable(del(i),draw_efun(i,df),"Delete this function")])); + df[funs[i].name]=true; + } + es.push(more(g,add_fun,"Add a new function")); + return es; +} + +function draw_fun(fun,dc,df) { + function check(el) { + return ifError(dc[fun.name], + "Function names must be distinct from category names", + ifError(df[fun.name],"Same function defined twice",el)); + } + return node("span",{}, + [check(ident(fun.name)),sep(" : "),draw_type(fun.type,dc)]); +} + +function draw_type(t,dc) { + var el=empty("span"); + function check(t,el) { + return ifError(!dc[t],"Undefined category",el); + } + for(var i in t) { + if(i>0) el.appendChild(sep(" → ")); + el.appendChild(check(t[i],ident(t[i]))); + } + return el; +} + +function edit_name(g,el) { + function change_name(name) { + if(name!=g.basename && name!="") { + var err=check_name(name,"Grammar"); + if(err) return err; + g.basename=name + reload_grammar(g); + } + return null; + } + string_editor(el,g.basename,change_name) +} +/* -------------------------------------------------------------------------- */ + +function draw_concrete(g,i) { + var conc=g.concretes[i]; + function edit_langcode(g,el) { + function change_langcode(code) { + var err=check_name(g.basename+code,"Name of concrete syntax"); + if(err) return err; + adjust_opens(conc,conc.langcode,code); + conc.langcode=code; + timestamp(conc); + reload_grammar(g); + } + string_editor(el,conc.langcode,change_langcode) + } + var kw_lincat=kw("lincat") + kw_lincat.title="The linearization type for each catagory in the abstract syntax is given here. [C.3.8]" + var kw_lin=kw("lin") + kw_lin.title="The linearization function for each function in the abstract syntax is given here. [C.3.9]" + var kw_param=kw("param") + kw_param.title="Parameter type definitions can be added here. [C.3.12]" + var kw_oper=kw("oper") + kw_oper.title="Operation definitions can be added here. [C.3.14]" + return div_id("file", + [kw("concrete "), + ident(g.basename), + editable("span",ident(conc.langcode),g, + edit_langcode,"Change language"), + kw(" of "),ident(g.basename),sep(" = "), + draw_timestamp(conc), + indent([extensible([kw("open "),draw_opens(g,i)])]), + indent([kw_lincat,draw_lincats(g,i)]), + indent([kw_lin,draw_lins(g,i)]), + indent([extensible([kw_param,draw_params(g,i)])]), + indent([extensible([kw_oper,draw_opers(g,i)])]), + exb_extra(g,i) + ]) +} + +var rgl_modules=["Paradigms","Syntax","Lexicon","Extra"]; + +function add_open(ci) { + return function (g,el) { + var conc=g.concretes[ci]; + var os=conc.opens; + var ds={}; + for(var i in os) ds[os[i]]=true; + var list=[] + for(var i in rgl_modules) { + var b=rgl_modules[i], m=b+conc.langcode; + if(!ds[m]) + list.push(li([a(jsurl("add_open2("+g.index+","+ci+",'"+m+"')"), + [text(m)])])); + } + if(list.length>0) { + var file=element("file"); + file.innerHTML=""; + file.appendChild(p(text("Pick a resource library module to open:"))); + file.appendChild(node("ul",{},list)); + } + } +} + +function add_open2(ix,ci,m) { + var g=local.get(ix); + var conc=g.concretes[ci]; + conc.opens || (conc.opens=[]); + conc.opens.push(m); + timestamp(conc); + save_grammar(g); + open_concrete(g,ci); +} + +function delete_open(g,ci,ix) { + with(g.concretes[ci]) opens=delete_ix(opens,ix); + timestamp(g.concretes[ci]); + reload_grammar(g); +} + +function draw_opens(g,ci) { + var conc=g.concretes[ci]; + var os=conc.opens || [] ; + var es=[]; + function del(i) { return function() { delete_open(g,ci,i); }} + var first=true; + for(var i in os) { + if(!first) es.push(sep(", ")) + es.push(deletable(del(i),ident(os[i]),"Don't open this module")); + first=false; + } + es.push(more(g,add_open(ci),"Open more modules")); + return indent(es); +} + +function draw_param(p,dp) { + function check(el) { + return ifError(dp[p.name],"Same parameter type defined twice",el); + } + return node("span",{},[check(ident(p.name)),sep(" = "),text(p.rhs)]); +} + +function add_param(g,ci,el) { + function add(s) { + var p=parse_param(s); + if(p.ok) { + g.concretes[ci].params.push(p.ok); + timestamp(g.concretes[ci]); + reload_grammar(g); + return null; + } + else + return p.error + } + string_editor(el,"",add); +} + +function edit_param(ci,i) { + return function (g,el) { + function replace(s) { + var p=parse_param(s); + if(p.ok) { + g.concretes[ci].params[i]=p.ok; + timestamp(g.concretes[ci]); + reload_grammar(g); + return null; + } + else + return p.error; + } + string_editor(el,show_param(g.concretes[ci].params[i]),replace); + } +} + + +function delete_param(g,ci,ix) { + with(g.concretes[ci]) params=delete_ix(params,ix); + timestamp(g.concretes[ci]); + reload_grammar(g); +} + +function draw_params(g,ci) { + var conc=g.concretes[ci]; + conc.params || (conc.params=[]); + var params=conc.params; + var es=[]; + var dp={}; + function del(i) { return function() { delete_param(g,ci,i); }} + function draw_eparam(i,dp) { + return editable("span",draw_param(params[i],dp),g,edit_param(ci,i),"Edit this parameter type"); + } + for(var i in params) { + es.push(div_class("param",[deletable(del(i),draw_eparam(i,dp),"Delete this parameter type")])); + dp[params[i].name]=true; + } + es.push(more(g,function(g,el) { return add_param(g,ci,el)}, + "Add a new parameter type")); + return indent(es); +} + +function delete_lincat(g,ci,cat) { + var i; + var c=g.concretes[ci]; + for(i=0;i<c.lincats.length && c.lincats[i].cat!=cat;i++); + if(i<c.lincats.length) c.lincats=delete_ix(c.lincats,i); + timestamp(c); + reload_grammar(g); +} + +function draw_lincats(g,i) { + var conc=g.concretes[i]; + function edit(c) { + return function(g,el) { + function check(s,cont) { + function check2(msg) { + if(!msg) { + if(c.template) conc.lincats.push({cat:c.cat,type:s}); + else c.type=s; + reload_grammar(g); + } + cont(msg); + } + check_exp(s,check2); + } + string_editor(el,c.type,check,true) + } + } + function del(c) { return function() { delete_lincat(g,i,c); } } + function dlc(c,cls) { + var t=editable("span",text_ne(c.type),g,edit(c),"Edit lincat for "+c.cat); + return node("span",{"class":cls}, + [ident(c.cat),sep(" = "),t]); + } + var dc=defined_cats(g); + function draw_lincat(c) { + var cat=c.cat; + var err=!dc[cat]; + var l1=dlc(c,"lincat"); + var l2= deletable(del(cat),l1,"Delete this lincat"); + var l=ifError(err,"lincat for undefined category",l2); + delete dc[cat]; + return node_sortable("lincat",cat,[l]); + } + function dtmpl(c) { + return wrap("div",dlc({cat:c,type:"",template:true},"template")); } + var lcs=map(draw_lincat,conc.lincats); + for(var c in dc) + lcs.push(dtmpl(c)); + function sort_lincats() { + conc.lincats=sort_list(this,conc.lincats,"cat"); + timestamp(conc); + save_grammar(g); + } + return indent_sortable(lcs,sort_lincats); +} + +/* -------------------------------------------------------------------------- */ + +function draw_oper(p,dp) { + function check(el) { + return ifError(dp[p.name],"Same operator definition defined twice",el); + } + return node("span",{},[check(ident(p.name)),text(" "),text(p.rhs)]); +} + +function check_oper(s,ok,err) { + var p=parse_oper(s); + function check2(msg) { + if(msg) err(msg); + else ok(p.ok) + } + if(p.ok) { + // Checking oper syntax by checking an expression with a local + // definition. Some valid opers will be rejected!! + var e=p.ok.name+" where { "+p.ok.name+" "+p.ok.rhs+" }"; + check_exp(e,check2); + } + else + err(p.error); +} + +function add_oper(g,ci,el) { + function check(s,cont) { + function ok(oper) { + g.concretes[ci].opers.push(oper); + timestamp(g.concretes[ci]); + reload_grammar(g); + cont(null); + } + check_oper(s,ok,cont) + } + string_editor(el,"",check,true); +} + +function edit_oper(ci,i) { + return function (g,el) { + function check(s,cont) { + function ok(oper) { + g.concretes[ci].opers[i]=oper; + timestamp(g.concretes[ci]); + reload_grammar(g); + cont(null); + } + check_oper(s,ok,cont) + } + string_editor(el,show_oper(g.concretes[ci].opers[i]),check,true); + } +} + +function delete_oper(g,ci,ix) { + with(g.concretes[ci]) opers=delete_ix(opers,ix); + timestamp(g.concretes[ci]); + reload_grammar(g); +} + +function draw_opers(g,ci) { + var conc=g.concretes[ci]; + conc.opers || (conc.opers=[]); + var opers=conc.opers; + var es=[]; + var dp={}; + function del(i) { return function() { delete_oper(g,ci,i); }} + function draw_eoper(i,dp) { + return editable("span",draw_oper(opers[i],dp),g,edit_oper(ci,i),"Edit this operator definition"); + } + for(var i in opers) { + es.push(node_sortable("oper",opers[i].name, + [deletable(del(i),draw_eoper(i,dp), + "Delete this operator definition")])); + dp[opers[i].name]=true; + } + es.push(more(g,function(g,el) { return add_oper(g,ci,el)}, + "Add a new operator definition")); + function sort_opers() { + conc.opers=sort_list(this,conc.opers,"name"); + timestamp(conc); + save_grammar(g); + } + return indent_sortable(es,sort_opers); +} + +function delete_lin(g,ci,fun) { + var i; + var c=g.concretes[ci]; + for(i=0;i<c.lins.length && c.lins[i].fun!=fun;i++); + if(i<c.lins.length) c.lins=delete_ix(c.lins,i); + timestamp(c); + reload_grammar(g); +} + +/* -------------------------------------------------------------------------- */ +function arg_names(type) { + function lower(s) { return s.toLowerCase(); } + var names=map(lower,type); + names.pop(); // remove result type + var n,count={},use={}; + for(var i in names) n=names[i],count[n]=0,use[n]=0; + for(var i in names) count[names[i]]++; + function unique(n) { + return count[n]>1 ? n+(++use[n]) : n; + } + return map(unique,names); +} + +function draw_lins(g,ci) { + var conc=g.concretes[ci]; + function edit(f) { + return function(g,el) { + function check(s,cont) { + function check2(msg) { + if(!msg) { + if(f.template) + conc.lins.push({fun:f.fun,args:f.args,lin:s}); + else { f.lin=s; f.eb_lin=null; } + reload_grammar(g); + } + cont(msg); + } + check_exp(s,check2); + } + string_editor(el,f.lin,check,true) + } + } + function del(fun) { return function () { delete_lin(g,ci,fun); } } + function dl(f,cls) { + var l=[ident(f.fun)] + for(var i in f.args) { + l.push(text(" ")); + l.push(ident(f.args[i])); + } + l.push(sep(" = ")); + var t=editable("span",text_ne(f.lin),g,edit(f),"Edit lin for "+f.fun); + appendChildren(t,exb_linbuttons(g,ci,f)); + l.push(t); + return node("span",{"class":cls},l); + } + var df=defined_funs(g); + function draw_lin(f) { + var fun=f.fun; + var err= !df[fun]; + var l= deletable(del(fun),dl(f,"lin"),"Delete this linearization function") + var l=ifError(err,"Function "+fun+" is not part of the abstract syntax",l); + delete df[fun]; + return node_sortable("lin",fun,[l]); + } + function largs(f) { + var funs=g.abstract.funs; + for(var i=0;i<funs.length && funs[i].name!=f;i++); + return arg_names(funs[i].type); + } + function dtmpl(f) { + return div_class("template", + [dl({fun:f,args:largs(f),lin:"",template:true},"template")]); + } + function sort_lins() { + conc.lins=sort_list(this,conc.lins,"fun"); + timestamp(conc); + save_grammar(g); + } + var ls=map(draw_lin,conc.lins); + for(var f in df) + ls.push(dtmpl(f)); + return indent_sortable(ls,sort_lins); +} + +/* -------------------------------------------------------------------------- */ + +function find_langcode(concs,langcode) { + for(var ci in concs) + if(concs[ci].langcode==langcode) + return concs[ci]; + return null; +} + +function cleanup_deleted(files) { + var keep={} + for(var i in files) keep[files[i]]=true; + //debug("cleanup_deleted "+JSON.stringify(files)) + //debug("keep "+JSON.stringify(keep)) + for(var i=0;i<local.count;i++) { + var g=local.get(i,null) + if(g && g.unique_name && !keep[g.unique_name+".json"]) { + debug("cleanup "+i+" "+g.unique_name); + remove_local_grammar(i) + } + } +} + +function grammar_index() { + var index={} + var count=local.count + for(var i=0;i<count;i++) { + var g=local.get(i,null) + if(g && g.unique_name) index[g.unique_name]=i + } + return index +} + +function merge_grammar(i,newg) { + var oldg=local.get(i); + var keep=""; + debug("Merging at "+i); + if(oldg) { + oldg.basename=newg.basename; + if(newg.abstract.timestamp<oldg.abstract.timestamp) { + newg.abstract=newg.abstract + keep+=" "+oldg.basename + } + for(var ci in newg.concretes) { + var conc=newg.concretes[ci]; + var oldconc=find_langcode(oldg.concretes,conc.langcode); + if(oldconc && conc.timestamp<oldconc.timestamp) { + newg.concretes[ci]=oldconc; + keep+=" "+oldg.basename+conc.langcode; + } + } + } + local.put(i,newg) + return keep; +} + +function timestamp(obj,prop) { + obj[prop || "timestamp"]=Date.now(); +} + +function draw_timestamp(obj) { + var t=obj.timestamp; + return node("small",{"class":"modtime"}, + [text(t ? " -- "+new Date(t).toLocaleString() : "")]); +} + +/* -------------------------------------------------------------------------- */ + +function delete_ix(old,ix) { + var a=[]; + for(var i in old) if(i!=ix) a.push(old[i]); + return a; +} + +function sort_list(list,olditems,key) { + var items=[]; + function find(fun) { + for(var i=0;i<olditems.length;i++) + if(olditems[i][key]==fun) return olditems[i]; + return null; + } + for(var el=list.firstChild;el;el=el.nextSibling) { + var name=el.getAttribute("ident") + if(name) { + var old=find(name); + if(old) items.push(old) + else debug("Bug: did not find "+name+" while sorting"); + } + } + if(items.length==olditems.length) + return items; + else { + debug("Bug: length changed while sorting") + return olditems; + } +} + +function string_editor(el,init,ok,async) { + var p=el.parentNode; + function restore() { + e.parentNode.removeChild(e); + el.style.display=""; + } + function done() { + var edited=e.it.value; + restore(); + function cont(msg) { if(msg) start(msg); } + if(async) ok(edited,cont) + else cont(ok(edited)); + return false; + } + function start(msg) { + el.style.display="none"; + m.innerHTML=msg; + insertAfter(e,el); + e.it.focus(); + } + var m=empty_class("span","error_message"); + var i=node("input",{"class":"string_edit",name:"it",value:init},[]); + if(init.length>10) i.size=init.length+5; +// var i=node("textarea",{name:"it",rows:"2",cols:"60"},[text(init)]); + var e=node("form",{}, + [i, + node("input",{type:"submit",value:"OK"},[]), + button("Cancel",restore), + text(" "), + m]) + e.onsubmit=done + start(""); +} + +function ifError(b,msg,el) { return b ? inError(msg,el) : el; } + +function inError(msg,el) { + return node("span",{"class":"inError",title:msg},[el]); +} + +function kw(txt) { return wrap_class("span","kw",text(txt)); } +function sep(txt) { return wrap_class("span","sep",text(txt)); } +function ident(txt) { return wrap_class("span","ident",text(txt)); } +function indent(cs) { return div_class("indent",cs); } + +function indent_sortable(cs,sort) { + var n= indent(cs); + n.onsort=sort; + return n; +} + +function node_sortable(cls,name,ls) { + return node("div",{"class":cls,"ident":name},ls); +} + +function extensible(cs) { return div_class("extensible",cs); } + +function more(g,action,hint) { + var b=node("span",{"class":"more","title":hint || "Add more"}, + [text(" + ")]); + b.onclick=function() { action(g,b); } + return b; +} + +function text_ne(s) { // like text(s), but force it to be non-empty + return text(s ? s : "\xa0\xa0\xa0") +} + +function editable(tag,cs,g,f,hint) { + var b=edit_button(function(){f(g,e)},hint); + var e=node(tag,{"class":"editable"},[cs,b]); + //e.onclick=b.onclick; + return e; +} + +function edit_button(action,hint) { + var b=node("span",{"class":"edit","title":hint || "Edit"},[text("%")]); + b.onclick=action; + return b; +} + +function deletable(del,el,hint) { + var b=node("span",{"class":"delete",title:hint || "Delete"},[text("×")]) + b.onclick=del; + return node("span",{"class":"deletable"},[b,el]) +} + +function touch_edit() { + var b=node("input",{type:"checkbox"},[]); + function touch() { + document.body.className=b.checked ? "nohover" : "hover"; + } + b.onchange=touch; + insertAfter(b,editor); + insertAfter(wrap("small",text("Enable editing on touch devices. ")),b); +} +/* --- DOM Support ---------------------------------------------------------- */ + +function div_id(id,cs) { return node("div",{id:id},cs); } +function div_class(cls,cs) { return node("div",{"class":cls},cs); } +function a(url,linked) { return node("a",{href:url},linked); } +function ul(lis) { return node("ul",{},lis); } +function li(xs) { return node("li",{},xs); } +function table(rows) { return node("table",{},rows); } +function td_right(cs) { return node("td",{"class":"right"},cs); } +function jsurl(js) { return "javascript:"+js; } + +function hidden(name,value) { + return node("input",{type:"hidden",name:name,value:value},[]) +} + +function insertBefore(el,ref) { ref.parentNode.insertBefore(el,ref); } + +function insertAfter(el,ref) { + ref.parentNode.insertBefore(el,ref.nextSibling); +} +/* -------------------------------------------------------------------------- */ + +function download_from_cloud() { + var newdir="/tmp/"+location.hash.substr(1) + + function download2(olddir) { + //debug("Starting grammar sharing in the cloud") + if(newdir!=olddir) link_directories(newdir,download3) + else download4() + } + function download3() { + //debug("Uploading local grammars to cloud"); + upload_json(download4) + } + function download4() { + //debug("Downloading grammars from the cloud"); + download_json() + } + + with_dir(download2) +} + +/* --- Initialization ------------------------------------------------------- */ + +//document.body.appendChild(empty_id("div","debug")); + +function dir_bugfix() { + // remove trailing newline caused by bug in older version + var dir=local.get("dir"); + if(dir) { + var n=dir.length; + while(dir[dir.length-1]=="\n" || dir[dir.length-1]=="\r") + dir=dir.substr(0,dir.length-1) + if(dir.length<n) { + debug("removing trailing newline") + local.put("dir",dir); + } + //debug("Server directory: "+JSON.stringify(dir)) + } + else debug("No server directory yet") +} + +if(editor) { + initial_view(); + touch_edit(); + dir_bugfix(); +} + +//console.log("hi") diff --git a/src/www/gfse/example_based.js b/src/www/gfse/example_based.js new file mode 100644 index 000000000..5922b60ea --- /dev/null +++ b/src/www/gfse/example_based.js @@ -0,0 +1,158 @@ + +var example_based=[]; + +/* +-- cat lincat fun lin fun cat cat +environ :: ([(CId, CId)],[(CId, Expr)],[((CId, CId), [CId])]) -> Environ +*/ +function exb_state(g,ci) { + var conc=g.concretes[ci] + function show_list(show1,xs) { + return "["+map(show1,xs).join(",")+"]"; + } + function show_fun(fun) { + var t=fun.type + var res=t[t.length-1] + var args=t.slice(0,length-1); + return "(("+fun.name+","+res+"),["+args.join(",")+"])" + } + function show_lincat(lincat) { + return "("+lincat.cat+","+lincat.type+")" + } + function show_lin(lin) { + return "("+lin.fun+","+(lin.eb_lin||"?")+")" + } + function show_funs(funs) { return show_list(show_fun,funs) } + function show_lincats(lincats) { return show_list(show_lincat,lincats); } + function show_lins(lins) { return show_list(show_lin,lins) } + return "("+show_lincats(conc.lincats) + +","+show_lins(conc.lins) + +","+show_funs(g.abstract.funs)+")" +} + +function exb_call(g,ci,command,args,cont) { + var url=window.exb_url || "exb/exb.fcgi"; + var q=encodeArgs(args); + var cmd="?command="+command+"&state="+encodeURIComponent(exb_state(g,ci))+q; + http_get_json(url+cmd,cont) +} + +function ask_possibilities(g,ci) { + var conc=g.concretes[ci]; + + function show_poss(poss) { + //debug("possibilities: "+JSON.stringify(poss)) + var exready={} + for(var i in poss[0]) exready[poss[0][i]]=true; + var testable={} + for(var i in poss[1]) testable[poss[1][i]]=true; + example_based[ci]={exready:exready,testable:testable} + conc.example_based=true; + conc.example_lang=g.concretes[0].langcode; + reload_grammar(g); + } + + exb_call(g,ci,"possibilities",{},show_poss) +} + +function exb_extra(g,ci) { + var conc=g.concretes[ci]; + function stop_exb() { + conc.example_based=false; + reload_grammar(g); + } + + function exblangmenu() { + function opt(conc) { return option(conc.langcode,conc.langcode); } + // skip target language + var m =node("select",{},map(opt,g.concretes)); + m.onchange=function() { conc.example_lang=m.value } + return m + } + + function ask_poss() { ask_possibilities(g,ci) } + + if(navigator.onLine && conc.example_based && !example_based[ci]) ask_poss(); + return conc.langcode=="Eng" + ? indent([text("Example based editing: "), + conc.example_based + ? node("span",{},[button("Stop",stop_exb), + text(" Example language: "), + exblangmenu() + ]) + : button("Start",ask_poss)]) + : text("") +} + +function exb_linbuttons(g,ci,f) { + var conc=g.concretes[ci]; + var fun=f.fun; + var eb=example_based[ci]; + var exb_output; + function fill_example(maybetree) { + var tree=maybetree.Just + if(tree) { + if(f.template) + conc.lins.push({fun:f.fun,args:f.args, + lin:tree[0],eb_lin:tree[1]}); + else { + f.lin=tree[0]; + f.eb_lin=tree[1]; + } + ask_possibilities(g,ci) + } + else exb_output.innerHTML="Bug: no tree found" + } + function show_example(example){ + exb_output.innerHTML=""; + var s=prompt(example[1]); + if(s) { + var t=function_type(g,fun); + var abscat=t[t.length-1] + var cat=cat_lincat(conc,abscat) + exb_output.innerHTML="..."; + //server.parse({from:"ParseEng",cat:cat,input:s},fill_example) + exb_call(g,ci,"abstract_example", + {cat:cat,input:s, + params:"["+f.args.join(",")+"]", + abstract:example[0]}, + fill_example) + } + } + function by_example() { + var dir=local.get("dir") + if(dir) { + if(exb_output) { + exb_output.innerHTML="..."; + exb_call(g,ci,"provide_example", + {lang:g.basename+conc.example_lang, + fun:fun, + grammar:dir+"/"+g.basename+".pgf"}, + show_example) + } + } + else exb_output.innerHTML="Compile the grammar first!" + } + function show_test(txt) { + exb_output.innerHTML=""; + exb_output.appendChild(text(txt)) + } + function test_it(b) { + if(exb_output) { + exb_output.innerHTML="..."; + exb_call(g,ci,"test_function",{fun:fun},show_test) + } + } + var buttons=[]; + if(conc.example_based && eb) { + if(eb.exready[fun]) + buttons.push(button("By example",by_example)) + if(eb.testable[fun] && f.eb_lin) { + var b=button("Test it",test_it); + buttons.push(b) + } + var exb_output=node("span",{"class":"exb_output"},[]); + buttons.push(exb_output) + } + return buttons +} diff --git a/src/www/gfse/gf_abs.js b/src/www/gfse/gf_abs.js new file mode 100644 index 000000000..4efaab89a --- /dev/null +++ b/src/www/gfse/gf_abs.js @@ -0,0 +1,226 @@ +/* Abstract syntax for a small subset of GF grammars in JavaScript */ + +function defined_cats(g) { + var dc={}; + with(g.abstract) + for(var i in cats) dc[cats[i]]=true; + return dc; +} + +function defined_funs(g) { + var df={}; + with(g.abstract) + for(var i in funs) df[funs[i].name]=true; + return df; +} + +function function_type(g,fun) { + with(g.abstract) + for(var i in funs) if(funs[i].name==fun) return funs[i].type + return null; +} + +function cat_lincat(conc,cat) { + with(conc) + for(var i in lincats) if(lincats[i].cat==cat) return lincats[i].type + return null; +} + +function rename_category(g,oldcat,newcat) { + function rename_cats(cats) { + for(var i in cats) if(cats[i]==oldcat) cats[i]=newcat; + } + function rename_type(t) { + for(var i in t) if(t[i]==oldcat) t[i]=newcat; + } + function rename_funs(funs) { + for(var i in funs) rename_type(funs[i].type) + } + function rename_abstract(a) { + rename_cats(a.cats); + rename_funs(a.funs); + } + function rename_lincat(lc) { + if(lc.cat==oldcat) lc.cat=newcat; + } + function rename_concrete(c) { + for(var i in c.lincats) rename_lincat(c.lincats[i]); + } + function rename_concretes(cs) { + for(var i in cs) rename_concrete(cs[i]); + } + rename_abstract(g.abstract) + rename_concretes(g.concretes); + return g; +} + +function rename_function(g,oldfun,newfun) { + function rename_lin(lin) { + if(lin.fun==oldfun) lin.fun=newfun; + } + function rename_concrete(c) { + for(var i in c.lins) rename_lin(c.lins[i]); + } + for(var i in g.concretes) rename_concrete(g.concretes[i]); + return g; +} + +function change_lin_lhs(g,fun) { + function change_lin(lin) { + if(lin.fun==fun.name) lin.args=arg_names(fun.type); + } + function change_concrete(c) { + for(var i in c.lins) change_lin(c.lins[i]); + } + for(var i in g.concretes) change_concrete(g.concretes[i]); + return g; +} + +/* --- Parsing -------------------------------------------------------------- */ + +// GF idenfifier syntax: +var lex_id=/^[A-Za-z][A-Za-z0-9_']*$/ +// See https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions + +function check_id(s) { return lex_id.test(s); } + +function check_name(s,kind) { + return check_id(s) + ? null + : s+"? "+kind+" names must start with a letter and can contain letters, digits, _ and '" +} + + +function parse_fun(s) { + var ws=s.split(/\s+/); + var fun={name:"",type:[]}; + /* Use a state machine to parse function definitions */ + /* f : T1 -> ... -> Tn */ + var state="name"; + var ok=true; + for(var i=0;ok && i<ws.length;i++) { + if(ws[i]!="") { + switch(state) { + case "name": fun.name=ws[i]; state=":"; break; + case ":": ok=ws[i]==":"; state="type"; break; + case "type": fun.type.push(ws[i]); state="->"; break; + case "->": ok=ws[i]=="->"; state="type"; break; + } + } + } + var err=check_name(fun.name,"Function"); + if(err) return {error: err}; + return ok && state=="->" + ? {ok:fun} + : { error : "Fun : Cat<sub>1</sub> -> ... -> Cat<sub>n</sub>" } +} + + +function parse_param(s) { + var ws=s.split("="); + if(ws.length==2) { + var name=ws[0].trim(); + var err=check_name(name,"Parameter type"); + return err ? { error:err } : { ok: { name:name,rhs:ws[1].trim() } } + } + else + return { error: "P = C1 | ... | Cn" } +} + +function parse_oper(s) { + var i=s.indexOf(" "); + var operr = { error: "op = expr" } + if(i>0 && i<s.length-1) { + var name=s.substr(0,i).trim(); + var rhs=s.substr(i).trim(); + var err=check_name(name,"Operator"); + return err + ? {error:err} + : rhs!="" ? {ok: {name:name, rhs:rhs}} + : operr + } + else return operr + +} + +/* --- Print as plain text (normal GF concrete syntax) ---------------------- */ + +function show_type(t) { + var s=""; + for(var i in t) { + if(i>0) s+=" -> "; + s+=t[i]; + } + return s; +} + +function show_fun(fun) { + return fun.name+" : "+show_type(fun.type); +} + +function show_grammar(g) { + return show_abstract(g)+"\n"+show_concretes(g) +} + +function show_abstract(g) { +// var startcat= g.abstract.cats.length==1 ? g.abstract.cats[0] : g.abstract.startcat; + var startcat= g.abstract.startcat || g.abstract.cats[0]; + return "abstract "+g.basename+" = {\n\n" + +"flags coding = utf8 ;\n\n" + +show_startcat(startcat) + +show_cats(g.abstract.cats) + +show_funs(g.abstract.funs) + +"}\n"; +} + +function show_startcat(startcat) { + return startcat ? "flags startcat = "+startcat+";\n\n" : ""; +} + +function show_cats(cats) { + return cats.length>0 ? "cat\n "+cats.join("; ")+";\n\n" : ""; +} + +function show_funs(funs) { return show_list("fun",show_fun,funs); } + +function show_concretes(g) { + return map(show_concrete(g.basename),g.concretes).join("\n\n"); +} + +function show_concrete(basename) { + return function(conc) { + return "--# -path=.:present\n" + + "concrete "+basename+conc.langcode+" of "+basename+" =" + +show_opens(conc.opens) + +" {\n\nflags coding = utf8 ;\n\n" + +show_params(conc.params) + +show_lincats(conc.lincats) + +show_opers(conc.opers) + +show_lins(conc.lins) + +"}\n" + } +} + +function show_list(kw,show1,list) { + return list.length>0 + ? kw+"\n "+map(show1,list).join(";\n ")+";\n\n" + : "" +} + +function show_opens(opens) { + return opens && opens.length>0 ? "\n\nopen "+opens.join(", ")+" in" : "" +} + +function show_params(params) { return show_list("param",show_param,params); } +function show_lincats(lincats) { return show_list("lincat",show_lincat,lincats); } +function show_opers(opers) { return show_list("oper",show_oper,opers); } +function show_lins(lins) { return show_list("lin",show_lin,lins); } + + +function show_param(p) { return p.name + " = " + p.rhs; } +function show_oper(p) { return p.name + " " + p.rhs; } +function show_lincat(p) { return p.cat + " = " + p.type; } + +function show_lin(lin) { + return lin.fun + " " + lin.args.join(" ")+ " = " + lin.lin; +}
\ No newline at end of file diff --git a/src/www/gfse/gfse.manifest b/src/www/gfse/gfse.manifest new file mode 100644 index 000000000..60c6f3757 --- /dev/null +++ b/src/www/gfse/gfse.manifest @@ -0,0 +1,4 @@ +CACHE MANIFEST +# 5 +NETWORK: +* diff --git a/src/www/gfse/grammars.cgi b/src/www/gfse/grammars.cgi new file mode 100644 index 000000000..9f1aa22a6 --- /dev/null +++ b/src/www/gfse/grammars.cgi @@ -0,0 +1,19 @@ +#!/bin/bash +echo "Content-Type: text/javascript" +echo "" + +case "$QUERY_STRING" in + jsonp=*) prefix="${QUERY_STRING#jsonp=}("; suffix=")" ;; + *) prefix=""; suffix="" +esac + +echo -n "$prefix" +sep="[" +for g in *.pgf ; do + echo -n "$sep\"$g\"" + sep=", " +done +echo "]$suffix" +#echo "/*" +#set +#echo "*/" diff --git a/src/www/gfse/index.html b/src/www/gfse/index.html new file mode 100644 index 000000000..c683a1ebf --- /dev/null +++ b/src/www/gfse/index.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html> <!-- manifest="gfse.manifest" --> +<head> +<title>GF online editor for simple multilingual grammars</title> +<meta charset="UTF-8"> +<link rel="stylesheet" type="text/css" href="editor.css" title="Cloud"> +<link rel="alternate stylesheet" type="text/css" href="molto.css" title="MOLTO"> + +<link rel=author href="http://www.cse.chalmers.se/~hallgren/" title="Thomas Hallgren"> + +<meta name = "viewport" content = "width = device-width"> +<meta http-equiv="Content-type" content="text/html;charset=UTF-8"> + +</head> + +<body class=hover> + +<h2>GF online editor for simple multilingual grammars</h2> +<div id=editor> +</div> +<small class="hidden"> +<span class=more>+</span>=Add an item, +<span class=delete>×</span>=Delete item, +<span class=edit>%</span>=Edit item. +</small> +<small class="ifhover">Hover over items for hints and editing options.</small> + +<noscript> +This page does not work without JavaScript. +</noscript> + +<hr> +<div class=modtime><small> +HTML +<!-- hhmts start --> Last modified: Mon Oct 10 19:24:05 CEST 2011 <!-- hhmts end --> +</small></div> +<a href="about.html">About</a> +<pre id=debug></pre> +<script type="text/javascript" src="config.js"></script> <!-- optional --> +<script type="text/javascript" src="support.js"></script> +<script type="text/javascript" src="localstorage.js"></script> +<script type="text/javascript" src="gf_abs.js"></script> +<script type="text/javascript" src="example_based.js"></script> +<script type="text/javascript" src="editor.js"></script> +<script type="text/javascript" src="cloud2.js"></script> +<script type="text/javascript" src="sort.js"></script> +</body> +</html> diff --git a/src/www/gfse/localstorage.js b/src/www/gfse/localstorage.js new file mode 100644 index 000000000..31201998c --- /dev/null +++ b/src/www/gfse/localstorage.js @@ -0,0 +1,21 @@ + +// We use localStorage to store the grammars, +// see http://diveintohtml5.org/storage.html + +var local={ + prefix:"gf.editor.simple.grammar", + get: function (name,def) { + var id=this.prefix+name + return localStorage[id] ? JSON.parse(localStorage[id]) : def; + }, + put: function (name,value) { + var id=this.prefix+name; + localStorage[id]=JSON.stringify(value); + }, + remove: function(name) { + var id=this.prefix+name; + localStorage.removeItem(id); + }, + get count() { return this.get("count",0); }, + set count(v) { this.put("count",v); } +} diff --git a/src/www/gfse/molto.css b/src/www/gfse/molto.css new file mode 100644 index 000000000..052dda431 --- /dev/null +++ b/src/www/gfse/molto.css @@ -0,0 +1,82 @@ +body { color: #413b36; + background: #fffcfa; + } +h1 { font-size: 175%; } +h1,h2,h3,h4,small { font-family: sans-serif; } +h1,h2,h3,h4,a { color: #5c1a1a; } + +h1:first-child, h2:first-child { margin-top: 0; margin-bottom: 1ex; } + +#editor { max-width: 50em; } +div.grammar { border: 2px solid #b09779; background: #642121; } +div.files { margin: 0 8px 8px 8px; } + +div#file { border: 2px solid #b0977d; border-top-width: 0; } +pre.plain { border: 2px solid #b0977d; } +div#file, pre.plain { background: #fffcfa; padding: 0.6ex; } + +.slideshow .hidden { display: none; } + +img.right, div.right, div.modtime { float: right; } +.modtime { color: #999; white-space: nowrap; } + +/*div.namebar { background: #642121; }*/ +div.namebar table { width: 100%; } +.namebar h3 { margin: 0; color: white; } + +td.right { text-align: right; } + +.kw { font-weight: bold; font-family: sans-serif; color: #642121; } +.sep { font-weight: bold; color: #642121; } + +div.indent { padding-left: 1em; min-width: 1em; min-height: 1em; } + +/* +div.fun, div.param, div.lincat, div.oper, div.lin +{ padding-left: 2em; text-indent: -2em; } +*/ +.more, .delete { font-weight: bold; font-family: sans-serif; } +.more, .delete, .edit { cursor: pointer; } + +.hover .more, .hover .delete, .hover .edit { visibility: hidden } + +.hover .hidden, .nohover .ifhover { display: none; } + +.editable:hover, .deletable:hover { background: #ff9; } +.namebar .editable:hover { background: #04b; } + +.extensible:hover .more,.editable:hover > .edit ,.deletable:hover > .delete + { visibility: visible; } + +.more { color: green; } +.edit { color: orange; } +.delete { color: red; } +.error_message,.inError { color: red; } +.template, .template .sep { color: #999; } +form { display: inline-block; } + +table.tabs { + width: 100%; + border-width: 0; border-spacing: 0; empty-cells: show; +} + +table.tabs td { text-align: center; border: 2px solid #b09779; padding: 2px; } +table.tabs td.active { background: white; border-bottom-width: 0; } +table.tabs td.inactive { + background: #e1e1e1; + border-top-color: #b09779; border-left-color: #b09779; border-right-color: #b09779; +} + + table.tabs td.gap +{ border-top-width: 0; border-left-width: 0; border-right-width: 0; } + +table.tabs input[type=button] { + border: 0; + background: inherit; + color: #642121; + font-size: inherit; + font-weight: bold; + /*text-decoration: underline;*/ +} + +input.string_edit { font-family: inherit; font-size: inherit; }
\ No newline at end of file diff --git a/src/www/gfse/save.hs b/src/www/gfse/save.hs new file mode 100644 index 000000000..0472ff5e8 --- /dev/null +++ b/src/www/gfse/save.hs @@ -0,0 +1,25 @@ +import System(getArgs) +import CGI(getQuery,string) +import MUtils(apSnd) + +main = save2 =<< getArgs + +{- +save1 [dir] = + do fs@[ns,_] <- readIO =<< getContents + nes <- save_all fs + putStrLn $ unwords nes + where + save_all [ns,cs] = mapM (write1 dir) (zip ns cs) +-} + +write1 dir (n,c) = + do writeFile (dir++"/"++ne) c + return ne + where + ne=if '.' `elem` n then n else n++".gf" + +save2 [dir] = + do nfs <- getQuery + nes <- mapM (write1 dir . apSnd string) nfs + putStrLn $ unwords nes diff --git a/src/www/gfse/share.html b/src/www/gfse/share.html new file mode 100644 index 000000000..260a7b066 --- /dev/null +++ b/src/www/gfse/share.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> +<html> <head> +<title>Download from Grammar Cloud</title> +<link rel="stylesheet" type="text/css" href="editor.css" title="Cloud"> +<link rel="alternate stylesheet" type="text/css" href="molto.css" title="MOLTO"> + +</head> + +<body> +<h1><img src="P/1307545048_weather_09.png" alt="">Download from Grammar Cloud</h1> + +<pre id=debug></pre> + +<hr> +<address></address> +<!-- hhmts start --> Last modified: Mon Oct 10 20:29:01 CEST 2011 <!-- hhmts end --> +<script type="text/javascript" src="support.js"></script> +<script type="text/javascript" src="localstorage.js"></script> +<script type="text/javascript" src="gf_abs.js"></script> +<script type="text/javascript" src="editor.js"></script> +<script type="text/javascript" src="cloud2.js"></script> +<script type="text/javascript" src="sort.js"></script> +<script type="text/javascript"> +download_from_cloud(); +</script> +</body> +</html> diff --git a/src/www/gfse/slideshow.js b/src/www/gfse/slideshow.js new file mode 100644 index 000000000..307bcd98f --- /dev/null +++ b/src/www/gfse/slideshow.js @@ -0,0 +1,86 @@ + +var internet_explorer=navigator.appName=="Microsoft Internet Explorer"; + +/* How to change opacity in IE: +http://joseph.randomnetworks.com/archives/2006/08/16/css-opacity-in-internet-explorer-ie/ +*/ + +var set_opacity = + internet_explorer + ? function(el,o) { el.style.filter="alpha(opacity="+Math.round(o*100)+")";} + : function(el,o) { el.style.opacity=o; }; + +function start_slideshow(img,options) { + var p=img.parentNode; + if(p.tagName=="A") p=p.parentNode; + var is=p.getElementsByTagName("img"); + if(is.length>1) { + var cur=0; + var w=img.width; + var h=img.height; + //p.style.position="relative"; + p.style.minWidth=w+"px"; + p.style.minHeight=h+"px"; + var images=[]; + for(var i=0;i<is.length;i++) { + images[i]=is[i]; + var c=images[i]; + if(internet_explorer) c.style.zoom=1; + c.style.position="absolute"; + } + var timeout=1000*(options.delay || 5); + var ft=options.fade==null ? 1 : options.fade; + var tick=function() { + var c=images[cur]; + cur= (cur+1) % images.length; + var n=images[cur]; + set_opacity(n,0); + //n.style.position="static"; + n.style.zIndex=1; + n.className=""; + if(n.width>w) { w=n.width; p.style.minWidth=w+"px"; } + if(n.height>h) { h=n.height; p.style.minHeight=h+"px"; } + c.style.position="absolute"; + c.style.zIndex=0; + fade(n,0,1,ft,function() { + if(c.width>n.width || c.height>n.height) fade(c,1,0,ft,null); + else set_opacity(c,0); }); + //debug.innerHTML=w+"x"+h; + //for(var i=0;i<images.length;i++) + //debug.appendChild(text(" "+images[i].style.position)); + } + //var debug=document.createElement("div"); + //p.parentNode.insertBefore(debug,p); + //debug.innerHTML=w+"x"+h; + setInterval(tick,timeout); + } + //else alert("No slideshow!"); +} + +function fade(el,start,stop,t,after) { + // el: which element to fade + // start: starting opacity [0..1] + // stop: ending opacity [0..1] + // t: duration of fade (in seconds), default 1s + // after: function to call when done fading, optional + var dt=40; // Animation granularity, 1/40ms = 25fps + el.step=(stop-start)*dt/(1000*(t==null ? 1 : t)); + el.stop=stop; + //alert("fade "+start+" "+stop+" "+el.step); + var done=function() { + clearInterval(el.timer); + el.timer=null; + if(after) after(); + } + var f=function() { + var next=el.current+el.step; + if(next>=1) { next=1; done(); } + if(next<=0) { next=0; done(); } + set_opacity(el,next); + el.current=next + } + if(!el.timer) { + el.current=start; + el.timer=setInterval(f,dt); + } +} diff --git a/src/www/gfse/upload.cgi b/src/www/gfse/upload.cgi new file mode 100644 index 000000000..87b868f53 --- /dev/null +++ b/src/www/gfse/upload.cgi @@ -0,0 +1,252 @@ +#!/bin/bash + +bin=/Users/hallgren/www/bin + +charset="UTF-8" +AUTOHEADER=no + +. $bin/cgistart.sh +export LC_CTYPE="UTF-8" +style_url="editor.css" + +tmp="$documentRoot/tmp" + +make_dir() { + dir="$(mktemp -d "$tmp/gfse.XXXXXXXXXX")" +# chmod a+rxw "$dir" + chmod a+rx "$dir" + cp "grammars.cgi" "$dir" +} + + +check_grammar() { + pagestart "Uploaded" +# echo "$PATH_INFO" + chgrp everyone "$dir" + chmod g+ws "$dir" + umask 002 +# files=( $(Reg from-url | LC_CTYPE=sv_SE.ISO8859-1 ./save "$dir") ) + files=( $(LC_CTYPE=sv_SE.ISO8859-1 ./save "$dir") ) + gffiles=( ) + otherfiles=( ) + for f in ${files[*]} ; do + case "$f" in + *.gf) gffiles=( ${gffiles[*]} "$f" ) ;; + *) otherfiles=( ${otherfiles[*]} "$f" ) ;; + esac + done + + if [ ${#otherfiles} -gt 0 -a -n "$PATH_INFO" ] ; then + echo "Use the following link for shared access to your grammars from multiple devices:" + begin ul + case "$SERVER_PORT" in + 80) port="" ;; + *) port=":$SERVER_PORT" + esac + parent="http://$SERVER_NAME$port${REQUEST_URI%/upload.cgi/tmp/gfse.*}" + cloudurl="$parent/share.html#${dir##*/}" + li; link "$cloudurl" "$cloudurl" + end + #begin dl + #dt ; echo "◂"; link "javascript:history.back()" "Back to Editor" + #end + fi + + cd $dir + if [ ${#gffiles} -gt 0 ] ; then + begin pre + echo "gf -s -make ${gffiles[*]}" + if gf -s -make ${gffiles[*]} 2>&1 ; then + end + h3 OK + begin dl + [ -z "$minibar_url" ] || { dt; echo "▸"; link "$minibar_url?/tmp/${dir##*/}/" "Minibar"; } + [ -z "$transquiz_url" ] || { dt; echo "▸"; link "$transquiz_url?/tmp/${dir##*/}/" "Translation Quiz"; } + [ -z "$gfshell_url" ] || { dt; echo "▸"; link "$gfshell_url?dir=${dir##*/}" "GF Shell"; } + dt ; echo "◂"; link "javascript:history.back()" "Back to Editor" + + end + sed=(); + for pgf in *.pgf ; do + sed=("${sed[@]}" -e "s%$pgf%<a href=\"${dir##*/}/$pgf\">$pgf</a>%") + done + begin pre + ls -l *.pgf | sed "${sed[@]}" + end + else + end + begin h3 class=error_message; echo Error; end + for f in ${gffiles[*]} ; do + h4 "$f" + begin pre class=plain + cat -n "$f" + end + done + fi + fi + begin div class=footer + hr + date + end +# begin pre ; env + endall +} + +error400() { + echo "Status: 400" + pagestart "Error" + echo "What do you want?" + endall +} + +error404() { + echo "Status: 404" + pagestart "Not found" + echo "Not found" + endall +} + +if [ -z "$tmp" ] || ! [ -d "$tmp" ] ; then + pagestart "Error" + begin pre + echo "upload.cgi is not properly configured:" + if [ -z "$tmp" ] ; then + echo "cgiconfig.sh must define tmp" + elif [ ! -d "$tmp" ] || [ ! -w "$tmp" ] ; then + echo "$tmp must be a writeable directory" + fi + # cgiconfig.sh should define minibar & gfshell to allow grammars to be tested. + endall +else +case "$REQUEST_METHOD" in + POST) + case "$PATH_INFO" in + /tmp/gfse.*) + style_url="../../$style_url" + dir="$tmp/${PATH_INFO##*/}" + check_grammar + ;; + *) + make_dir + echo >&2 "Using temporary directory $dir" + check_grammar + rm -rf "$dir" + esac + ;; + GET) + case "$QUERY_STRING" in + dir) make_dir + ContentType="text/plain" + cgiheaders + echo_n "/tmp/${dir##*/}" + ;; + ls=*) + dir=$(qparse "$QUERY_STRING" ls) + case "$dir" in + /tmp/gfse.*) # shouldn't allow .. in path !!! + path="$documentRoot$dir" + if [ -d "$path" ] ; then + ContentType="text/plain; charset=$charset" + cgiheaders + cd "$path" + shopt -s nullglob + echo_n *-*.json + else + error404 + fi + ;; + *) error400 + esac + ;; + rmdir=*) + dir=$(qparse "$QUERY_STRING" rmdir) + case "$dir" in + /tmp/gfse.*) # shouldn't allow .. in path !!! + path="$documentRoot$dir" + if [ -d "$path" ] ; then + ContentType="text/plain; charset=$charset" + cgiheaders + if [ -h "$path" ] ; then + cd "$path" + cd .. + rm "$path" + else + cd "$path" + shopt -s nullglob + rm *.gf *.gfo *-*.json *.pgf grammars.cgi + cd .. + rmdir "$path" + fi + newdir=$(qparse "$QUERY_STRING" newdir) + case "$newdir" in + /tmp/gfse.*) # shouldn't allow .. in path !!! + newnode="${newdir##*/}" + oldnode="${path##*/}" + ln -s "$newnode" "$oldnode" + esac + else + error404 + fi + ;; + *) error400 + esac + ;; + download=*) + file=$(qparse "$QUERY_STRING" download) + case "$file" in + /tmp/gfse.*/*.json) # shouldn't allow .. in path !!! + path="$documentRoot$file" + if [ -r "$path" ] ; then + ContentType="text/javascript; charset=$charset" + cgiheaders + cat "$path" + else + error404 + fi + ;; + *) error400 + esac + ;; + rm=*) + file=$(qparse "$QUERY_STRING" rm) + case "$file" in + /tmp/gfse.*/*.json) # shouldn't allow .. in path !!! + path="$documentRoot$file" + if [ -r "$path" ] ; then + ContentType="text/javascript; charset=$charset" + cgiheaders + rm "$path" + else + error404 + fi + ;; + *) error400 + esac + ;; + cc=*) + # Just to check an expression for syntax errors + exp=$(qparse "$QUERY_STRING" cc) + ContentType="text/plain; charset=$charset" + cgiheaders + echo "cc $exp" | GF_RESTRICTED=True gf -run + ;; + "") + case "$PATH_INFO" in + /tmp/gfse.*/*.pgf) + path="$documentRoot$PATH_INFO" + if [ -r $path ] ; then + ContentType="application/binary" + cgiheaders + cat "$path" + else + error404 + fi + ;; + *) + error400 + esac + ;; + *) error400 + esac +esac +fi diff --git a/src/www/index.html b/src/www/index.html new file mode 100644 index 000000000..b1aa24028 --- /dev/null +++ b/src/www/index.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> + +<!-- This is the start page served by "gf -server" --> + +<title>GF web service</title> +<h1>GF web service</h1> + +<h2>Available web services</h2> + +<ul> + <li><a href="minibar/minibar.html">Minibar</a> + <li><a href="gfse/">GF online editor for simple multilingual grammars</a> +</ul> + + +<hr> + +<a href="http://www.grammaticalframework.org/">Grammatical Framework</a> diff --git a/src/www/minibar/about.html b/src/www/minibar/about.html new file mode 100644 index 000000000..69fba5a9d --- /dev/null +++ b/src/www/minibar/about.html @@ -0,0 +1,180 @@ +<!DOCTYPE html> +<html> <head> +<title>About Minibar</title> +<link rel=stylesheet type="text/css" href="minibar.css"> +<meta charset="UTF-8"> +</head> + +<body> +<h1>About Minibar</h1> + +<a href="minibar.html">Minibar</a> is an alternative implementation of the +<a href="http://www.grammaticalframework.org/">GF</a> web app +<a href="http://www.grammaticalframework.org:41296/fridge/">Fridge Poetry</a>. +It doesn't do everything the original Fridge Poetry does (e.g. drag-and-drop is missing), +so I refer to it as a minibar rather than a full refrigerator :-) + +<p> +Some implementation details: + +<ul class=space> + <li>It is implemented directly in JavaScipt. It does not use Google Web Toolkit or any big JavaScript libraries. + <li>It has been tested and found to work in the following browsers: + <ul> + <li>On the Mac: Firefox 3.5 & 3.6, Safari 4.0, Opera 10.10 and + Google Chrome 4.0.249.49. + <li>On Linux: Firefox 3.0.18 & 3.5, Opera 10.10. + <li>On the Android Dev Phone: Android Mobile Safari 3.0.4 & 3.1.2 + and Android Opera Mini 4.2. + </ul> + It does not seem work in Internet Explorer 7 + (there are both styling and scripting issues). + There seems to be some rendering bugs in Chrome 5.0.342.9 β. + <li>The implementation consist of two JavaScript files: + <a href="minibar.js">minibar.js</a> and <a href="support.js">support.js</a> + The latter is also used in + <a href="http://spraakbanken.gu.se/swe/forskning/saldo/ordspel">a couple of + small web apps</a> based on the + <a href="http://spraakbanken.gu.se/sal/ws/">SALDO web services</a>. + <li>To access the GF web service, it uses the + <a href="http://en.wikipedia.org/wiki/JSON#JSONP">JSONP method</a> + mentioned in the GF + web services paper, which allows the web app to be hosted on a different server + from the GF web service. (To demonstrate this, I put the Minibar demo on + www.cs.chalmers.se, while the GF server that it calls is on + www.grammaticalframework.org.) + <li>As an experiment, it does no use the <code>grammars.xml</code> file, + but instead calls a little CGI script, + <a href="http://www.grammaticalframework.org:41296/grammars/grammars.cgi.txt">grammars.cgi</a> + which lists the .pgf files in the directory, in JSONP format. + (Note: if you want to install this on your own computer, + <ul> + <li>if you click on the link, + the CGI script will be downloaded as <code>grammars.cgi.txt</code>, + but it should be called <code>grammars.cgi</code> and stored on the server + in the same directory as the grammar files. + <li>for CGI scripts to work with lighttpd, <code>"mod_cgi"</code> needs + to be included in the definition of <code>server.modules</code> in the + <code>lighttpd.conf</code> file.) + </ul> + <li>[Added 2010-02-16] There is a button for generating random sentences. + <li>[Added 2010-02-23] All translations are shown, not just the first one, + if there are multiple parses. + <li>[Added 2010-02-25] Next to each translation, there is now a little tree + icon that you can click on to see a drawing of an abstract syntax tree or a + parse tree. If you click on a drawing it collapses back into a tree icon. + <li>[Added 2010-04-09] Preparations to support different ways to access the + grammar: currently we access a PGF server via JSONP, but I would also like + to support AJAX, and local/downloaded JavaScript grammars. + <li>[Added 2010-04-19] A text entry field appears when you click in + the sentence area (with a dashed border). This allows you to enter words by + typing on the keyboard. As you start typing word magnets that don't match what + you are typing are removed. When only one magnet remains, you can press enter + to complete the word. + <li>[Added 2010-04-19] There is a menu for choosing the output language: + you can pick "All" to translate to all available languages, or pick one + particular language. + <li>[Added 2010-04-19] You can pass options to the function + <code>start_minibar</code> to customize the user interface. The default is + <code>{show_abstract:true,show_trees:true}</code> to show the abstract syntax + of parsed sentences, and to show icons that expand to syntax/parse trees next + each translation. + These features can be turned off by setting the fields to <code>false</code>. + <li>[Added 2010-04-30] The grammar menu is omitted if there is only one + grammar in the grammar list. + <li>[Added 2010-04-30] Fewer hardwired constants and new + <code>start_minibar</code> options (server, grammars_url, grammar_list, + show_grouped_translations, delete_button_text) to make + <code>minibar.js</code> more resuable.) + <li>[Added 2010-05-26] The magnets are now created with + <code><input type=button></code> tags to make them clickable in more + browsers. + <li>[Added 2010-05-26] The text entry field is now visible from the start, + and it is removed when no more words can be added to the sentence. When you + press enter, a word is added if there is only one magnet left, + <em>or</em> if what you have entered exactly matches one of the remaining + magnet. + <li>[Added 2010-05-28] Added a link to make it easy to try the same sentence in + <a href="http://translate.google.com">Google Translate</a>.This can be + turned off by passing the option <code>{try_google:false}</code> to + <code>start_minibar</code>. + <li>[Added 2010-06-02] Added support for Help and Feedback buttons, controlled + by the options <code>feedback_url</code> and <code>help_url</code> passed to + <code>start_minibar</code>. + <li>[Added 2010-06-02] New option: <code>default_source_language</code>. + <li>[Added 2010-09-10] Minibar now automatically uses + <a href="http://en.wikipedia.org/wiki/XMLHttpRequest">XHR</a> + instead of JSONP when possible (i.e. when the HTML document and the + PGF service are on the same server). + <li>[Added 2010-09-10] The default input language is now the user's preferred + language, if possible. This is implemented by consulting the + <code>userLanguage</code> field in the grammar info output by pgf-server. + <li>[Added 2010-10-27] Keyboard input and completion should now work much + more smoothly: + <ul> + <li>When you press space, the current word will be completed (if incomplete) + and a new magnet will be created. If there is more than one possible + completion, no magnet is created, but the common prefix of the possible + completions is added to the text box. + <li>Instead of asking the server for possible completions every time a new + letter is added to the curent word, minibar only ask for completions for + whole words and then filters the list locally when more letters are entered, + speeding things up when server responses are slow. + </ul> + <li>[Added 2010-10-27] Code restructuring: + <ul> + <li>The PGF server API has been moved to its own file: + <a href="pgf_online.js">pgf_online.js</a>. This + allows it to be reused in other applicaitons without importing the entire + minibar. It also allows minibar to be used with different server + interfaces. <a href="minibar.html">minibar.html</a> has been updated to + show how you use the new <a href="minibar.js">minibar.js</a> and + <a href="pgf_online.js">pgf_online.js</a>. + <li>The minibar code has been rewritten to avoid storing state information + in the document tree and accessing it by referring to named document + elements. The code now also avoids using string literals containing + the names of top-level functions to specify event handlers for buttons + and menus. (The code is no longer introspective, so α conversion + will not change its meaning.) + </ul> + <li>[Added 2010-11-09] Some new documentation: + <ul> + <li><a href="gf-web-api-examples.html">gf-web-api-examples.html</a>: + examples illustrating the PGF server API provided by + <a href="pgf_online.js">pgf_online.js</a>. + <li><a href="example.html">example.html</a>: a minimal example of a web + page that uses <a href="pgf_online.js">pgf_online.js</a> to talk to the + PGF server. + </ul> + <li>[Added 2011-03-03] Added a button to display word alignment. + <li>[Changed 2011-03-22] Don't force focus to the typed input field + after every word. On touch-based devices, the on-screen keyboard kept + popping up after every word, which was very annoying if you were + entering a sentence by tapping on the magnets. + <li>[Changed 2011-08-03] Moved the initialization code in minibar.html to + <a href="minibar_online.js">minibar_online.js</a>. + <li>[Changed 2011-08-08] For improved modularity and reusability, + two smaller objects have been factored out from the Minibar object: + Input and Translations. These have been placed in two separate files: + <a href="minibar_input.js">minibar_input.js</a> and + <a href="minibar_translations.js">minibar_translations.js</a>. + Some common auxiliary functions have also been moved to a separate file: + <a href="minibar_support.js">minibar_support.js</a>. + <li>[Added 2011-08-09] Added some <a href="minibar-api.html">Minibar API</a> + documentation. + <li>[Changed 2011-08-22] Quick fix to allow literals to be entered: + if you press Enter, the current word will be accepted, even if there are no + matching completions. + (You can now use names of people when constructing sentences in the Letter + grammar, for example.) +</ul> + +<hr> +<small class=modtime> +<!-- hhmts start --> Last modified: Mon Aug 22 19:31:37 CEST 2011 <!-- hhmts end --> +</small> +<address> +<a href="http://www.cs.chalmers.se/~hallgren/">TH</a> +<img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt=""></address> +</address> +</body> </html> diff --git a/src/www/minibar/align-btn.png b/src/www/minibar/align-btn.png Binary files differnew file mode 100644 index 000000000..ca6a391c1 --- /dev/null +++ b/src/www/minibar/align-btn.png diff --git a/src/www/minibar/brushed-metal.png b/src/www/minibar/brushed-metal.png Binary files differnew file mode 100644 index 000000000..c2f03fe7d --- /dev/null +++ b/src/www/minibar/brushed-metal.png diff --git a/src/www/minibar/example.html b/src/www/minibar/example.html new file mode 100644 index 000000000..7d78a7ef6 --- /dev/null +++ b/src/www/minibar/example.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<html> <head> +<title>PGF online server example</title> +<style type="text/css"> +body { background: #ddd; } +h1, h2, h3, small, th { font-family: sans-serif; } +div.modtime { float: right; } +.modtime { color: #666; white-space: nowrap; } +</style> +<script type="text/JavaScript" src="support.js"></script> +<script type="text/JavaScript" src="pgf_online.js"></script> +<script type="text/JavaScript"> + +var server_options={ + grammars_url: "http://www.grammaticalframework.org/grammars/", + grammar_list: ["Foods.pgf"] +} +var pgf_server = pgf_online(server_options); + +function call_server() { + pgf_server.parse({from:"FoodsEng",input:document.forms[0].input.value}, + show_output) +} + +function show_output(parsed) { + document.getElementById("output").innerHTML=parsed[0].trees[0] +} + +</script> + +</head> + +<body> +<h1>PGF online server example</h1> + +<form onsubmit="call_server(); return false"> +Input: +<input name=input size=50 value="this cheese is expensive"> +<input type=submit value=Parse> + +<p> +Output: +<span id=output></span> +</form> + + +<h2>Documentation</h2> +<ul> + <li><a href="gf-web-api-examples.html">GF Web API examples</a> +</ul> + +<hr> +<div class=modtime><small> +<!-- hhmts start --> Last modified: Wed Aug 3 16:52:51 CEST 2011 <!-- hhmts end --> + </small></div> +<address><a href="http://www.cse.chalmers.se/~hallgren/">TH</a></address> +</body> </html> diff --git a/src/www/minibar/feedback.cgi b/src/www/minibar/feedback.cgi new file mode 100644 index 000000000..0d382f22d --- /dev/null +++ b/src/www/minibar/feedback.cgi @@ -0,0 +1,44 @@ +#!/bin/bash + +bin=bin +AUTOHEADER=no +. $bin/cgistart.sh + +save_feedback() { +getquery + +if [ -n "$feedback_path" ] && + echo "t=$(date +%F+%T)&ip=$REMOTE_ADDR&$query&accept_language=$HTTP_ACCEPT_LANGUAGE&user_agent=$(echo -n $HTTP_USER_AGENT | plain2url)" >> "$feedback_path" +then + + pagestart "Thank you!" + echo "Your feedback has been saved." + begin script type="text/javascript" + echo "setTimeout(function(){window.close()},4000);" + end + pageend + +else + + pagestart "Feedback error" + echo "Your feedback could not be saved. Sorry." + p + tag 'input type=button onclick="javascript:history.back()" value="<- Go back"' + pageend + +fi +} + +view_feedback() { + charset="UTF-8" + pagestart "Collected Feedback" + begin pre class=feedbacklist + Reg show reverse drop color_depth,pixel_depth,outer_size,inner_size,available_screen_size from-url <"$PATH_TRANSLATED" | plain2html + end + pageend +} + +case "$PATH_TRANSLATED" in + "") save_feedback ;; + *) view_feedback +esac diff --git a/src/www/minibar/feedback.html b/src/www/minibar/feedback.html new file mode 100644 index 000000000..94b15a482 --- /dev/null +++ b/src/www/minibar/feedback.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> <head> +<title>Feedback</title> +<link rel=stylesheet type="text/css" href="minibar.css"> +<script type="text/JavaScript" src="support.js"></script> +<script type="text/JavaScript" src="minibar.js"></script> +<meta name = "viewport" content = "width = device-width"> + +</head> + +<body onload="prefill_feedback_form()"> + +<h2><span id=grammar></span> Feedback</h2> + +<form class=feedback name=feedback action="feedback.cgi" method="post"> +<input type=hidden name="grammar"> + +<p> +<input type=hidden name="from"> <span class=field id=from>...</span> input: +<input type=hidden name="input"> <span class=field id=input>...</span> + +<div id=translation_box> +<p><input type=hidden name="to"> <span class=field id="to">...</span> translation: +<input type=hidden name="translation"> <span class=field id=translation>...</span> + +<p><label accesskey="S">Suggest a better translation: +<textarea rows=3 name="improvement"></textarea></label> +</div> + +<p><label accesskey="C">Comments: +<br><textarea rows=5 name="comment"></textarea></label> + +<p> +<input type=submit value="Submit Feedback"> +<input type=button value="Cancel" onclick="window.close()"> + + +<input type=hidden name="inner_size"> +<input type=hidden name="outer_size"> +<input type=hidden name="screen_size"> +<input type=hidden name="available_screen_size"> +<input type=hidden name="color_depth"> +<input type=hidden name="pixel_depth"> + +</form> +</body> + +</html> diff --git a/src/www/minibar/gf-web-api-examples.html b/src/www/minibar/gf-web-api-examples.html new file mode 100644 index 000000000..194967de0 --- /dev/null +++ b/src/www/minibar/gf-web-api-examples.html @@ -0,0 +1,151 @@ +<!DOCTYPE html> +<html> +<head> +<title>GF web services API examples</title> +<meta charset="UTF-8"> + +<style type="text/css"> +body { background: #ddd; } +h1, h2, h3, small, th { font-family: sans-serif; } + +dt { background: #cef; } +dt.js { background: white; margin-bottom: 1ex; } +dt.js em { color: #36f; } +dd { background: #ffc; margin-top: 1ex; margin-bottom: 1ex; } + +dl.apiexamples>dt, dl.apiexamples>dd { font-family: monospace; } +dl.apiexamples>dd { white-space: pre; } + +div.modtime { float: right; } +.modtime { color: #666; white-space: nowrap; } + +@media projection { + div.intro { display: none; } + + body { + font-size: 150%; + } + + h2 { page-break-before: always; } + + dl.apiexamples dd { + page-break-after: always; + /*border-style: none;*/ + } +} + +</style> + +<body> +<h1>GF web services API examples</h1> + +GF can be used interactively from the GF Shell. Some of the functionality +availiable in the GF shell is also available via the GF web services API. + +<p> +The +<a href="http://code.google.com/p/grammatical-framework/wiki/GFWebServiceAPI">GF +Web Service API page</a> describes the calls supported by the GF web service +API. Below, we illustrate these calls by examples, and also show +how to make these calls from JavaScript using the API defined in +<a href="pgf_online.js"><code>pgf_online.js</code></a>. + +<p> +<strong>Note</strong> that <code>pgf_online.js</code> was initially developed +with one particular web application in mind (the minibar), so the server API was +incomplete. It was simplified and generalized in August 2011 to support the +full API. + +<dl> + <dt class=js>These boxes show what the calls look like in the JavaScript + API defined in <code>pgf_online.js</code>. + <dt>These boxes show the corresponding URLs sent to the PGF server. + <dd>These boxes show the JSON (JavaScript data structures) returned by the PGF + server. This will be passed to the callback function supplied in the + call. +</dl> + +<h2>Initialization</h2> +<dl class=apiexamples> + <dt class=js> + <em>// Select which server and grammars to use:</em> + <br>var server_options = { + <br> grammars_url: "http://www.grammaticalframework.org/grammars/", + <br> grammar_list: ["Foods.pgf"] <em>// It's ok to skip this</em> + <br>} + <br>var server = pgf_online(server_options); +</dl> + +<h2>Examples</h2> + +<dl class=apiexamples> + <dt class=js> <em>// Get the list of available grammars</em> + <br>server.get_grammarlist(callback) + <dt>http://localhost:41296/grammars/grammars.cgi + <dd>["Foods.pgf","Phrasebook.pgf"] + <dt class=js> <em>// Select which grammar to use</em> + <br>server.switch_grammar("Foods.pgf") + <dt class=js><em>// Get list of concrete languages and other grammar info</em> + <br>server.grammar_info(callback) + <dt>http://localhost:41296/grammars/Foods.pgf + <dd>{"name":"Foods", + "userLanguage":"FoodsEng", + "categories":["Comment","Float","Int","Item","Kind","Quality","String"], + "functions":["Boring","Cheese","Delicious","Expensive","Fish","Fresh", + "Italian","Mod","Pizza","Pred","That","These","This","Those","Very", + "Warm","Wine"], + "languages":[{"name":"FoodsBul","languageCode":""}, + {"name":"FoodsEng","languageCode":"en-US"}, + {"name":"FoodsFin","languageCode":""}, + {"name":"FoodsSwe","languageCode":"sv-SE"}, + ...] +} + <dt class=js><em>// Get a random syntax tree</em> + <br>server.get_random({},callback) + <dt>http://localhost:41296/grammars/Foods.pgf?command=random + <dd>[{"tree":"Pred (That Pizza) (Very Boring)"}] + <dt class=js><em>// Linearize a syntax tree</em> + <br>server.linearize({tree:"Pred (That Pizza) (Very Boring)",to:"FoodsEng"},callback) + <dt>http://localhost:41296/grammars/Foods.pgf?command=linearize&tree=Pred+(That+Pizza)+(Very+Boring)&to=FoodsEng + <dd>[{"to":"FoodsEng","text":"that pizza is very boring"}] + <dt class=js>server.linearize({tree:"Pred (That Pizza) (Very Boring)"},callback) + <dt>http://localhost:41296/grammars/Foods.pgf?command=linearize&tree=Pred+(That+Pizza)+(Very+Boring) + <dd>[{"to":"FoodsBul","text":"онази пица е много еднообразна"}, + {"to":"FoodsEng","text":"that pizza is very boring"}, + {"to":"FoodsFin","text":"tuo pizza on erittäin tylsä"}, + {"to":"FoodsSwe","text":"den där pizzan är mycket tråkig"}, + ... +] + <dt class=js><em>// Parse a string</em> + <br>server.parse({from:"FoodsEng",input:"that pizza is very boring"},callback) + <dt>http://localhost:41296/grammars/Foods.pgf?command=parse&input=that+pizza+is+very+boring&from=FoodsEng + <dd>[{"from":"FoodsEng", + "brackets":{"cat":"Comment","fid":10,"index":0,"children":[{"cat":"Item","fid":7,"index":0,"children":[{"token":"that"},{"cat":"Kind","fid":6,"index":0,"children":[{"token":"pizza"}]}]},{"token":"is"},{"cat":"Quality","fid":9,"index":0,"children":[{"token":"very"},{"cat":"Quality","fid":8,"index":0,"children":[{"token":"boring"}]}]}]}, + "trees":["Pred (That Pizza) (Very Boring)"]}] + <dt class=js><em>// Translate to all available languages</em> + <br>server.translate({from:"FoodsEng",input:"that pizza is very boring"},callback) + <dd>... + <dt class=js><em>// Translate to one language</em> + <br>server.translate({input:"that pizza is very boring", from:"FoodsEng", to:"FoodsSwe"}, callback) + <dt>http://localhost:41296/grammars/Foods.pgf?command=translate&input=that+pizza+is+very+boring&from=FoodsEng&to=FoodsSwe + <dd>[{"from":"FoodsEng", + "brackets":{"cat":"Comment","fid":10,"index":0,"children":[{"cat":"Item","fid":7,"index":0,"children":[{"token":"that"},{"cat":"Kind","fid":6,"index":0,"children":[{"token":"pizza"}]}]},{"token":"is"},{"cat":"Quality","fid":9,"index":0,"children":[{"token":"very"},{"cat":"Quality","fid":8,"index":0,"children":[{"token":"boring"}]}]}]}, + "translations": + [{"tree":"Pred (That Pizza) (Very Boring)", + "linearizations": + [{"to":"FoodsSwe", + "text":"den där pizzan är mycket tråkig"}]}]}] + <dt class=js><em>// Get completions (what words could come next)</em> + <br>server.complete({from:"FoodsEng",input:"that pizza is very "},callback) + <dt>http://localhost:41296/grammars/Foods.pgf?command=complete&input=that+pizza+is+very+&from=FoodsEng + <dd>[{"from":"FoodsEng", + "brackets":{"cat":"_","fid":0,"index":0,"children":[{"cat":"Item","fid":7,"index":0,"children":[{"token":"that"},{"cat":"Kind","fid":6,"index":0,"children":[{"token":"pizza"}]}]},{"token":"is"},{"token":"very"}]}, + "completions":["boring","delicious","expensive","fresh","Italian","very","warm"], + "text":""}] +</dl> +<hr> +<div class=modtime><small> +<!-- hhmts start --> Last modified: Sun Aug 21 10:52:43 CEST 2011 <!-- hhmts end --> + </small></div> +<address><a href="http://www.cse.chalmers.se/~hallgren/">TH</a></address> +
\ No newline at end of file diff --git a/src/www/minibar/minibar-api.html b/src/www/minibar/minibar-api.html new file mode 100644 index 000000000..2c5a8ecc0 --- /dev/null +++ b/src/www/minibar/minibar-api.html @@ -0,0 +1,235 @@ +<!DOCTYPE html> +<html> +<head> +<title>GF web services API examples</title> +<meta charset="UTF-8"> + +<style type="text/css"> +body { background: #eee; } +h1, h2, h3, small, th { font-family: sans-serif; } +th { text-align: left; } +h1,h2 { border-bottom: 2px solid black } +dt { background: #cef; } +code { background: #ffc; } +dt.js { background: white; margin-bottom: 1ex; } +dt.js em { color: #36f; } +dd { background: #ffc; margin-top: 1ex; margin-bottom: 1ex; } + +dl.apiexamples>dt, dl.apiexamples>dd { font-family: monospace; } +dl.apiexamples>dd { white-space: pre; } + +table.border { border-collapse: collapse; margin-top: 1ex; margin-bottom: 1ex; } +table.border td, table.border th { border: 1px solid black; background: #fcfcfc; } + +div.modtime { float: right; } +.modtime { color: #666; white-space: nowrap; } + +</style> + +<body> +<h1>Minibar API</h1> + +The Minibar web app consists of the following objects: + +<ul> + <li><a href="#Minibar">Minibar</a> + <li><a href="#Input">Input</a> + <li><a href="#Translations">Translations</a> +</ul> + +They are described below. + +<h2 id=Minibar>The Minibar object</h2> + +<p> +This object implements the complete Minibar web app. It is defined in +<a href="minibar.js">minibar.js</a>. It also uses the <code>Input</code> +and <code>Translations</code> objects described below, and some auxiliary +functions defined in <a href="minibar_support.js">minibar_support.js</a> +and <a href="support.js">support.js</a>, so to use it in an +HTML file, you would normally include at least the following: + +<blockquote><pre> +<script type="text/JavaScript" src="minibar.js"></script> +<script type="text/JavaScript" src="minibar_input.js"></script> +<script type="text/JavaScript" src="minibar_translations.js"></script> +<script type="text/JavaScript" src="minibar_support.js"></script> +<script type="text/JavaScript" src="support.js"></script> +</pre></blockquote> + +<p> +For an example, see <a href="minibar.html">minibar.html</a>. + +<h3>Constructor</h3> + +<code>var minibar=new Minibar(server,options,target)</code> + +<ul> + <li><code>server</code> is the PGF service object. + <li><code>options</code> is an object where the following properties + can be set to override various default options: + <table class=border> + <tr><th>Option<th>Default<th>Description + <tr><td>show_abstract<td>false<td rowspan=3>See Translations, + not used directly by Minibar + <tr><td>show_trees<td>false + <tr><td>show_grouped_translations<td>true + <tr><td>delete_button_text<td>"⌫"<td rowspan=3>See Input, + not used directly by Minibar + <tr><td>default_source_language<td>null + <tr><td>random_button<td>true + <tr><td>try_google<td>true<td>Include a button to try the current + sentence in Google Translate + <tr><td>feedback_url<td>null<td>Include a button to open a feedback + form. The HTTP server must be configured to handle form submissions + for this to work. + <tr><td>help_url<td>null<td>Include a button to open a help text. + </table> + <li><code>target</code> is the <code>id</code> of the HTML element inside + which the minibar user interface is created. It can be omitted if + the <code>id</code> is <code>minibar</code>. The HTML document should + contain something like this: + <blockquote><code><div id="minibar"></div></code></blockquote> +</ul> + +<h3>Methods</h3> +There are several internal methods, but since this is a self-contained +web app, there is usually no need to call any methods from outside. + +<h2 id=Input>The Input object</h2> + +This object handles user input. Text can be entered by typing or by clicking +on the "refrigerator magnets". +<p> +It is defined in +<a href="minibar_input.js">minibar_input.js</a>. +It also uses some auxiliary functions defined +in <a href="minibar_support.js">minibar_support.js</a> +and <a href="support.js">support.js</a>, so to use it in an +HTML file, you would normally include at least the following: + +<blockquote><pre> +<script type="text/JavaScript" src="minibar_input.js"></script> +<script type="text/JavaScript" src="minibar_support.js"></script> +<script type="text/JavaScript" src="support.js"></script> +</pre></blockquote> + +<h3>Constructor</h3> + +<code>var input=new Input(server,translations,options)</code> + +<ul> + <li><code>server</code> is the PGF service object + <li><code>options</code> is an object where the following properties + can be set to override various default options: + <table class=border> + <tr><th>Option<th>Default<th>Description + <tr><td>delete_button_text<td>"⌫"<td>the label for the button that deletes the last word + <tr><td>default_source_language<td>null<td>the concrete language to + use for input in case the user's browers doesn't supply a suitable + default. If none is provided the first language in alphabetical + order will be used. + <tr><td>random_button<td>true<td>include a button to generate a + random sentence + </table> + + <li><code>translations</code> is the object that is notified when the input + has changed. In the minibar, this is the object that display translations, but + other apps might of course use the entered text for other purposes. + The following methods will be called: + <ul> + <li><code>translations.clear()</code> is called when there no entered + text. + <li><code>translations.translateFrom({from:<var>conc</var>,input:<var>string</var>})</code> + is called when the user has entered some text. The <code>from</code> + property is the name of the concrete syntax and the <code>input</code> + property is the entered text. + </ul> +</ul> + +<h3>Properties and user interface</h3> + +The <code>input</code> object created by the <code>Input</code> constructor +contains two field that the caller should add to the user interface: +<ul> + <li><code>input.main</code> is the main user interface where the current + input and the refrigerator magnets are displayed. + <li><code>input.menus</code> contains the menu for selecting input language, + and buttons for deleting the last word, clearing the input and generating + a random sentence (if enabled in the options) +</ul> + +<h3>Methods</h3> + +<ul> + <li><code>input.change_grammar(grammar_info)</code> should be called + after a different grammar is selected in the <code>server</code> object. It + will clear away old input and magnets, and update the input language menu + with the languages available in the new grammar. + +</ul> + +<h2 id=Translations>The Translations object</h2> + +This object display translations. It is defined in +<a href="minibar_translations.js">minibar_translations.js</a>. +It also uses some auxiliary functions defined +in <a href="minibar_support.js">minibar_support.js</a> +and <a href="support.js">support.js</a>, so to use it in an +HTML file, you would normally include at least the following: + +<blockquote><pre> +<script type="text/JavaScript" src="minibar_input.js"></script> +<script type="text/JavaScript" src="minibar_support.js"></script> +<script type="text/JavaScript" src="support.js"></script> +</pre></blockquote> + +<h3>Constructor</h3> +<code>var translations=new Translations(server,options)</code> +<ul> + <li><code>server</code> is the PGF service object. + <li><p><code>options</code> is an object where the following properties + can be set to override various default options: + <table class=border> + <tr><th>Option<th>Default<th>Description + <tr><td>show_abstract<td>false<td>show the abstract syntax in addition + to the concrete syntax for the translations + <tr><td>show_trees<td>false<td>add buttons to display syntax trees + next to translations. + <tr><td>show_grouped_translations<td>true<td>in case there are + multiple translations, group them by concrete language + </table> + +</ul> + +<h3>Properties and user interface</h3> + + +The <code>translations</code> object created by the +<code>Translations</code> constructor contains two field that the caller +should add to the user interface: +<ul> + <li><code>input.main</code> is the main user interface where the current + translations are displayed. + <li><code>input.menus</code> contains the menu for selecting target language. +</ul> + +<h3>Methods</h3> +<ul> + <li><code>translations.change_grammar(grammar_info)</code> should be called + after a different grammar is selected in the <code>server</code> object. It + will clear away old translations and update the target language menu + with the languages available in the new grammar. +</ul> + + +<hr> +<div class=modtime> +<small class=modtime> +HTML <!-- hhmts start --> Last modified: Sun Aug 21 19:11:35 CEST 2011 <!-- hhmts end --> +</small> +</div> + +<address> +<a href="Http://www.cse.chalmers.se/~hallgren/">TH</a> +</address> diff --git a/src/www/minibar/minibar.css b/src/www/minibar/minibar.css new file mode 100644 index 000000000..4d2094066 --- /dev/null +++ b/src/www/minibar/minibar.css @@ -0,0 +1,53 @@ +body { + background: #ccc url("brushed-metal.png"); +} + +h1, h2, h3, small, th { font-family: sans-serif; } + +th, td { vertical-align: baseline; text-align: left; } + +div#surface { + min-height: 3ex; + margin: 5px; + padding: 5px; + border: 3px dashed #e0e0e0; +} + +div#words { + min-height: 3ex; + margin: 5px; + padding: 6px; + border: 3px solid #e0e0e0; +} + +div.word, span.word, div#words div, div#words input[type=button] { + display: inline-block; + font-family: sans-serif; + font-size: 100%; + background-color: white; + border: 1px solid black; + padding: 3px; + margin: 3px; +} + +.invalid { color: red; } + +div.modtime { float: right; } +.modtime { color: #666; white-space: nowrap; } + +ul.space>li { margin-top: 0.75ex; } + +div#saldospel input[type=button] { font-size: 100%; } + +div#saldospel input.correct { color: green; } +div#saldospel input.incorrect { color: red; } + +#surface input[type=text] { width: 5em; } + +.feedback textarea { width: 95%; } + +span.field { background-color: #eee; } + +pre.feedbacklist { background: white } + +img.button { padding: 1px; }
\ No newline at end of file diff --git a/src/www/minibar/minibar.html b/src/www/minibar/minibar.html new file mode 100644 index 000000000..086b8fad6 --- /dev/null +++ b/src/www/minibar/minibar.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> +<head> +<title>Minibar</title> +<link rel=stylesheet type="text/css" href="minibar.css"> + +<meta name = "viewport" content = "width = device-width"> +<meta charset="UTF-8"> + +</head> + +<body> +<h2>Minibar online</h2> +<div id=minibar></div> + +<noscript>This page doesn't works unless JavaScript is enabled.</noscript> + +<hr> + +<small> +[<a href="about.html">About Minibar</a> +| <a href="http://www.grammaticalframework.org:41296/fridge/">Original Fridge Poetry</a> +& <a href="http://www.grammaticalframework.org:41296/translate/">Translator</a>] +</small> +<small class=modtime> +HTML <!-- hhmts start --> Last modified: Mon Aug 8 18:04:22 CEST 2011 <!-- hhmts end --> +</small> +<address> +<a href="http://www.cse.chalmers.se/~hallgren/">TH</a> +<img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt=""></address> + +<script type="text/JavaScript" src="support.js"></script> +<script type="text/JavaScript" src="minibar.js"></script> +<script type="text/JavaScript" src="minibar_input.js"></script> +<script type="text/JavaScript" src="minibar_translations.js"></script> +<script type="text/JavaScript" src="minibar_support.js"></script> +<script type="text/JavaScript" src="pgf_online.js"></script> +<script type="text/javascript" src="minibar_online.js"></script> + +</body> +</html> diff --git a/src/www/minibar/minibar.js b/src/www/minibar/minibar.js new file mode 100644 index 000000000..778a3957b --- /dev/null +++ b/src/www/minibar/minibar.js @@ -0,0 +1,176 @@ +/* minibar.js +needs: minibar_support.js, minibar_input.js, minibar_translations.js, support.js +*/ + +/* +// This is essentially what happens when you call start_minibar: +if(server.grammar_list) grammars=server.grammar_list; +else grammars=server.get_grammarlist(); +show_grammarlist(grammars) +select_grammar(grammars[0]) +grammar_info=server.get_languages() +show_languages(grammar_info) +new_language() +complete_output=get_completions() +show_completions(complete_output) +*/ + +// For backward compatibility: +function start_minibar(server,opts,target) { + if(target) opts.target=target; + return new Minibar(server,opts); +} + +/* --- Main Minibar object -------------------------------------------------- */ +function Minibar(server,opts) { + // Contructor, typically called when the HTML document is loaded + + /* --- Configuration ---------------------------------------------------- */ + + // default values for options: + this.options={ + target: "minibar", + try_google: true, + feedback_url: null, + help_url: null + } + + // Apply supplied options + if(opts) for(var o in opts) this.options[o]=opts[o]; + + /* --- Creating the components of the minibar --------------------------- */ + this.translations=new Translations(server,this.options) + this.input=new Input(server,this.translations,this.options) + + /* --- Creating user interface elements --------------------------------- */ + + this.menubar=empty("div"); + this.extra=div_id("extra"); + + this.minibar=element(this.options.target); + this.minibar.innerHTML=""; + with(this) { + appendChildren(menubar,[input.menus,translations.menus,input.buttons]) + appendChildren(minibar,[menubar,input.main,translations.main,extra]); + append_extra_buttons(extra,options); + } + + /* --- Minibar client state initialisation ------------------------------ */ + this.grammar=null; + + this.server=server; + + /* --- Main program, this gets things going ----------------------------- */ + with(this) { + if(server.grammar_list) show_grammarlist(server.grammar_list); + else server.get_grammarlist(bind(show_grammarlist,this)); + } +} + +Minibar.prototype.show_grammarlist=function(grammars) { + this.grammar_menu=empty_id("select","grammar_menu"); + with(this) { + if(grammars.length>1) { + function opt(g) { return option(g,g); } + appendChildren(grammar_menu,map(opt,grammars)); + grammar_menu.onchange= + bind(function() { select_grammar(grammar_menu.value); },this); + insertFirst(menubar,grammar_menu); + insertFirst(menubar,text("Grammar: ")); + } + if(options.help_url) + menubar.appendChild(button("Help",bind(open_help,this))); + select_grammar(grammars[0]); + } +} + +Minibar.prototype.select_grammar=function(grammar_name) { + var t=this; + //debug("select_grammar "); + function change_grammar() { + t.server.grammar_info(bind(t.change_grammar,t)); + } + t.server.switch_grammar(grammar_name,change_grammar); +} + +Minibar.prototype.change_grammar=function(grammar_info) { + var t=this; + with(t) { + //debug("show_languages "); + grammar=grammar_info; + + input.change_grammar(grammar) + translations.change_grammar(grammar) + } +} + +Minibar.prototype.append_extra_buttons=function(extra,options) { + with(this) { + if(options.try_google) + extra.appendChild(button("Try Google Translate",bind(try_google,this))); + if(options.feedback_url) + appendChildren(extra,[text(" "),button("Feedback",bind(open_feedback,this))]); + } +} + +Minibar.prototype.try_google=function() { + with(this) { + var to=translations.target_lang(); + var s=input.current.input; + if(input.surface.typed) s+=input.surface.typed.value; + var url="http://translate.google.com/?sl=" + +langpart(input.current.from,grammar.name); + if(to!="All") url+="&tl="+to; + url+="&q="+encodeURIComponent(s); + window.open(url); + } +} + +Minibar.prototype.open_help=function() { + with(this) open_popup(options.help_url,"help"); +} + +Minibar.prototype.open_feedback=function() { + with(this) { + // make the minibar state easily accessible from the feedback page: + minibar.state={grammar:grammar,current:input.current, + to:translations.to_menu.value, + translations:translations.translations}; + open_popup(options.feedback_url,'feedback'); + } +} + +// This function is called from feedback.html +function prefill_feedback_form() { + var state=opener_element("minibar").state; + var trans=state.translations; + var gn=state.grammar.name + var to=langpart(state.to,gn); + + var form=document.forms.namedItem("feedback"); + setField(form,"grammar",gn); + setField(form,"from",langpart(state.current.from,gn)); + setField(form,"input",state.current.input); + setField(form,"to",to); + if(to=="All") element("translation_box").style.display="none"; + else setField(form,"translation",trans.single_translation.join(" / ")); + + // Browser info: + form["inner_size"].value=window.innerWidth+"×"+window.innerHeight; + form["outer_size"].value=window.outerWidth+"×"+window.outerHeight; + form["screen_size"].value=screen.width+"×"+screen.height; + form["available_screen_size"].value=screen.availWidth+"×"+screen.availHeight; + form["color_depth"].value=screen.colorDepth; + form["pixel_depth"].value=screen.pixelDepth; + + window.focus(); +} + + +/* +se.chalmers.cs.gf.gwt.TranslateApp/align-btn.png + +GET /grammars/Foods.pgf?&command=abstrtree&tree=Pred+(This+Fish)+(Very+Fresh) +GET /grammars/Foods.pgf?&command=parsetree&tree=Pred+(This+Fish)+Expensive&from=FoodsAfr +GET /grammars/Foods.pgf?&command=alignment&tree=Pred+(This+Fish)+Expensive +*/ diff --git a/src/www/minibar/minibar_input.js b/src/www/minibar/minibar_input.js new file mode 100644 index 000000000..8075eb8ea --- /dev/null +++ b/src/www/minibar/minibar_input.js @@ -0,0 +1,277 @@ + +/* --- Input object --------------------------------------------------------- */ + +function Input(server,translations,opts) { // Input object constructor + this.server=server; + this.translations=translations; + + // Default values for options: + this.options={ + delete_button_text: "⌫", + default_source_language: null, + random_button: true, + } + + // Apply supplied options + if(opts) for(var o in opts) this.options[o]=opts[o]; + + // User interface elements + this.main=empty("div"); + this.menus=empty("span"); + this.buttons=empty("span"); + this.surface=div_id("surface"); + this.words=div_id("words"); + this.from_menu=empty("select"); + + with(this) { + appendChildren(main,[surface,words]); + appendChildren(menus,[text(" From: "),from_menu]) + appendChildren(buttons, + [button(options.delete_button_text,bind(delete_last,this),"H"), + button("Clear",bind(clear_all,this),"L")]); + if(options.random_button) + buttons.appendChild(button("Random",bind(generate_random,this),"R")); + } + + /* --- Input client state initialization --- */ + this.current={from: null, input: ""}; + this.previous=null; + + this.from_menu.onchange=bind(this.change_language,this); +} + +Input.prototype.change_grammar=function (grammar) { + update_language_menu(this.from_menu,grammar); + set_initial_language(this.options,this.from_menu,grammar); + this.change_language(); +} + +Input.prototype.change_language=function () { + this.current.from=this.from_menu.value; + this.clear_all(); +} + + +Input.prototype.clear_all1=function() { + with(this) { + remove_typed_input(); + current.input=""; + previous=null; + surface.innerHTML=""; + translations.clear(); + } +} + +Input.prototype.clear_all=function() { + with(this) { + clear_all1(); + get_completions(); + } +} + +Input.prototype.get_completions=function() { + with(this) { + //debug("get_completions "); + words.innerHTML="..."; + server.complete({from:current.from,input:current.input}, + bind(show_completions,this)); + } +} + +Input.prototype.show_completions=function(complete_output) { + with(this) { + //debug("show_completions "); + var completions=complete_output[0].completions; + var emptycnt=add_completions(completions) + if(true/*emptycnt>0*/) translations.translateFrom(current); + else translations.clear(); + if(surface.typed && emptycnt==completions.length) { + if(surface.typed.value=="") remove_typed_input(); + } + else add_typed_input(); + } +} + +Input.prototype.add_completions=function(completions) { + with(this) { + if(words.timeout) clearTimeout(words.timeout),words.timeout=null; + words.innerHTML=""; + words.completions=completions; + words.word=[]; + var t=surface.typed ? surface.typed.value : ""; + var emptycnt=0; + for(var i=0;i<completions.length;i++) { + var s=completions[i]; + if(s.length>0) { + var w=word(s); + words.appendChild(w); + words.word[i]=w; + } + else emptycnt++; + } + filter_completions(t,true); + return emptycnt; + } +} + +Input.prototype.filter_completions=function(t,dim) { + with(this) { + if(words.timeout) clearTimeout(words.timeout),words.timeout=null; + words.filtered=t; + //if(dim) debug('filter "'+t+'"'); + var w=words.word; + words.count=0; + var dimmed=0; + var prefix=""; // longest common prefix, for completion + for(var i=0;i<w.length;i++) { + var s=words.completions[i]; + var keep=hasPrefix(s,t); + if(keep) { + if(words.count==0) prefix=s; + else prefix=(commonPrefix(prefix,s)); + words.count++; + } + if(dim) { + w[i].style.opacity= keep ? "1" : "0.5"; + if(keep) w[i].style.display="inline"; + else dimmed++; + } + else + w[i].style.display=keep ? "inline" : "none"; + } + words.theword=prefix; + if(dimmed>0) + words.timeout=setTimeout(function(){ filter_completions(t,false)},1000); + } +} + + +Input.prototype.add_typed_input=function() { + with(this) { + if(!surface.typed) { + var inp=empty("input","type","text"); + inp.value=""; + inp.setAttribute("accesskey","t"); + inp.style.width="10em"; + inp.onkeyup=bind(complete_typed,this); + surface.appendChild(inp); + surface.typed=inp; + inp.focus(); + } + } +} + +Input.prototype.remove_typed_input=function() { + with(this) { + if(surface.typed) { + surface.typed.parentNode.removeChild(surface.typed); + surface.typed=null; + } + } +} + +Input.prototype.complete_typed=function(event) { + with(this) { + //element("debug").innerHTML=show_props(event,"event"); + var inp=surface.typed; + //debug('"'+inp.value+'"'); + var s=inp.value; + var ws=s.split(" "); + if(ws.length>1 || event.keyCode==13) { + if(ws[0]!=words.filtered) filter_completions(ws[0],true); + if(words.count==1) add_word(words.theword); + else if(event.keyCode==13) add_word(ws[0]) // for literals + else if(elem(ws[0],words.completions)) add_word(ws[0]); + else if(words.theword.length>ws[0].length) inp.value=words.theword; + } + else if(s!=words.filtered) filter_completions(s,true) + } +} + +Input.prototype.generate_random=function() { + var t=this; + function show_random(random) { + t.clear_all1(); + t.add_words(random[0].text); + } + + function lin_random(abs) { + t.server.linearize({tree:abs[0].tree,to:t.current.from},show_random); + } + t.server.get_random({},lin_random); +} + +Input.prototype.add_words=function(s) { + with(this) { + var ws=s.split(" "); + for(var i=0;i<ws.length;i++) + add_word1(ws[i]+" "); + get_completions(); + } +} + +Input.prototype.word=function(s) { + var t=this; + function click_word() { + if(t.surface.typed) t.surface.typed.value=""; + t.add_word(s); + } + return button(s,click_word); +} + +Input.prototype.add_word=function(s) { + with(this) { + add_word1(s+" "); + if(surface.typed) { + var s2; + if(hasPrefix(s2=surface.typed.value,s)) { + s2=s2.substr(s.length); + while(s2.length>0 && s2[0]==" ") s2=s2.substr(1); + surface.typed.value=s2; + } + else surface.typed.value=""; + } + get_completions(); + } +} + +Input.prototype.add_word1=function(s) { + with(this) { + previous={ input: current.input, previous: previous }; + current.input+=s; + var w=span_class("word",text(s)); + if(surface.typed) surface.insertBefore(w,surface.typed); + else surface.appendChild(w); + } +} + +Input.prototype.delete_last=function() { + with(this) { + if(surface.typed && surface.typed.value!="") + surface.typed.value=""; + else if(previous) { + current.input=previous.input; + previous=previous.previous; + if(surface.typed) { + surface.removeChild(surface.typed.previousSibling); + surface.typed.focus(); + } + else surface.removeChild(surface.lastChild); + translations.clear(); + get_completions(); + } + } +} + +/* --- Auxiliary functions -------------------------------------------------- */ + +function set_initial_language(options,menu,grammar) { + if(grammar.userLanguage) menu.value=grammar.userLanguage; + else if(options.default_source_language) { + for(var i=0;i<menu.options.length;i++) { + var o=menu.options[i].value; + var l=langpart(o,grammar.name); + if(l==options.default_source_language) menu.value=o; + } + } +} diff --git a/src/www/minibar/minibar_online.js b/src/www/minibar/minibar_online.js new file mode 100644 index 000000000..1c15e87bf --- /dev/null +++ b/src/www/minibar/minibar_online.js @@ -0,0 +1,25 @@ +// minibar_demo.js, assumes that minibar.js and pgf_online.js have been loaded. + +var online_options={ + //grammars_url: "http://www.grammaticalframework.org/grammars/", + //grammars_url: "http://tournesol.cs.chalmers.se:41296/grammars/", + //grammars_url: "http://localhost:41296/grammars/", + //grammar_list: ["Foods.pgf"], // leave undefined to get list from server +} + + +if(/^\?\/tmp\//.test(location.search)) { + online_options.grammars_url=location.search.substr(1); +} + +var server=pgf_online(online_options); + +var minibar_options= { + show_abstract: true, + show_trees: true, + show_grouped_translations: false, + default_source_language: "Eng", +//feedback_url: "feedback.html", + try_google: true +} +var minibar=new Minibar(server,minibar_options); diff --git a/src/www/minibar/minibar_support.js b/src/www/minibar/minibar_support.js new file mode 100644 index 000000000..a3fc078f7 --- /dev/null +++ b/src/www/minibar/minibar_support.js @@ -0,0 +1,46 @@ + +/* --- Auxiliary functions -------------------------------------------------- */ + +function langpart(conc,abs) { // langpart("FoodsEng","Foods") == "Eng" + return hasPrefix(conc,abs) ? conc.substr(abs.length) : conc; +} + +function update_language_menu(menu,grammar) { + // Replace the options in the menu with the languages in the grammar + var lang=grammar.languages; + menu.innerHTML=""; + + for(var i=0; i<lang.length; i++) { + var ln=lang[i].name; + if(!hasPrefix(ln,"Disamb")) { + var lp=langpart(ln,grammar.name); + menu.appendChild(option(lp,ln)); + } + } +} + +function button_img(url,action) { + var i=img(url); + i.setAttribute("class","button"); + i.setAttribute("onclick",action); + return i; +} + +function toggle_img(i) { + var tmp=i.src; + i.src=i.other; + i.other=tmp; +} + +function setField(form,name,value) { + form[name].value=value; + var el=element(name); + if(el) el.innerHTML=value; +} + +function open_popup(url,target) { + var w=window.open(url,target,'toolbar=no,location=no,status=no,menubar=no'); + w.focus(); +} + +function opener_element(id) { with(window.opener) return element(id); } diff --git a/src/www/minibar/minibar_translations.js b/src/www/minibar/minibar_translations.js new file mode 100644 index 000000000..ef6fbd701 --- /dev/null +++ b/src/www/minibar/minibar_translations.js @@ -0,0 +1,162 @@ +/* --- Translations object -------------------------------------------------- */ + +var tree_icon="tree-btn.png"; +var alignment_icon="align-btn.png"; + +function Translations(server,opts) { + this.server=server; + + // Default values for options: + this.options={ + show_abstract: false, + show_trees: false, + show_grouped_translations: true, + } + + // Apply supplied options + if(opts) for(var o in opts) this.options[o]=opts[o]; + + this.main=empty("div"); + this.menus=empty("span"); + + this.to_menu=empty_id("select","to_menu"); + + appendChildren(this.menus,[text(" To: "), this.to_menu]) + this.to_menu.onchange=bind(this.get_translations,this); + +} + +Translations.prototype.change_grammar=function(grammar) { + this.grammar=grammar; + + update_language_menu(this.to_menu,grammar); + insertFirst(this.to_menu,option("All","All")); + this.to_menu.value="All"; +} + +Translations.prototype.clear=function() { + this.main.innerHTML=""; +} + +Translations.prototype.translateFrom=function(current) { + this.current=current; + this.get_translations(); +} + +Translations.prototype.get_translations=function() { + with(this) { + var c=current; + if(options.show_grouped_translations) + server.translategroup({from:c.from,input:c.input}, + bind(show_groupedtranslations,this)); + else + server.translate({from:c.from,input:c.input}, + bind(show_translations,this)); + } +} + +Translations.prototype.tdt=function(tree_btn,txt) { + with(this) { + return options.show_trees ? tda([tree_btn,txt]) : td(txt); + } +} + +Translations.prototype.target_lang=function() { + with(this) return langpart(to_menu.value,grammar.name); +} + +Translations.prototype.show_translations=function(translationResults) { + with(this) { + var trans=main; + //var to=target_lang(); // wrong + var to=to_menu.value; + var cnt=translationResults.length; + //trans.translations=translations; + trans.single_translation=[]; + trans.innerHTML=""; + /* + trans.appendChild(wrap("h3",text(cnt<1 ? "No translations?" : + cnt>1 ? ""+cnt+" translations:": + "One translation:"))); + */ + for(p=0;p<cnt;p++) { + var tra=translationResults[p]; + if (tra.translations != null) { + for (q = 0; q < tra.translations.length; q++) { + var t = tra.translations[q]; + var lin=t.linearizations; + var tbody=empty("tbody"); + if(options.show_abstract && t.tree) + tbody.appendChild( + tr([th(text("Abstract: ")), + tdt(node("span",{},[abstree_button(t.tree), + alignment_button(t.tree)]), + text(" "+t.tree))])); + for(var i=0;i<lin.length;i++) { + if(lin[i].to==to) + trans.single_translation.push(lin[i].text); + if(to=="All" || lin[i].to==to) + tbody.appendChild(tr([th(text(langpart(lin[i].to,grammar.name)+": ")), + tdt(parsetree_button(t.tree,lin[i].to), + text(lin[i].text))])); + } + trans.appendChild(wrap("table",tbody)); + } + } + else if(tra.typeErrors) { + var errs=tra.typeErrors; + for(var i=0;i<errs.length;i++) + trans.appendChild(wrap("pre",text(errs[i].msg))) + } + } + } +} + +Translations.prototype.show_groupedtranslations=function(translationsResult) { + with(this) { + var trans=main; + var to=target_lang(); + //var to=to_menu.value // wrong + var cnt=translationsResult.length; + //trans.translations=translationsResult; + trans.single_translation=[]; + trans.innerHTML=""; + for(p=0;p<cnt;p++) { + var t=translationsResult[p]; + if(to=="All" || t.to==to) { + var lin=t.linearizations; + var tbody=empty("tbody"); + if(to=="All") tbody.appendChild(tr([th(text(t.to+":"))])); + for(var i=0;i<lin.length;i++) { + if(to!="All") trans.single_translation[i]=lin[i].text; + tbody.appendChild(tr([td(text(lin[i].text))])); + if (lin.length > 1) tbody.appendChild(tr([td(text(lin[i].tree))])); + } + trans.appendChild(wrap("table",tbody)); + } + } + } +} + + +function abstree_button(abs) { + var i=button_img(tree_icon,"toggle_img(this)"); + i.title="Click to display abstract syntax tree" + i.other=server.current_grammar_url+"?command=abstrtree&tree="+encodeURIComponent(abs); + return i; +} + +function alignment_button(abs) { + var i=button_img(alignment_icon,"toggle_img(this)"); + i.title="Click to display word alignment" + i.other=server.current_grammar_url+"?command=alignment&tree="+encodeURIComponent(abs); + return i; +} + +function parsetree_button(abs,lang) { + var i=button_img(tree_icon,"toggle_img(this)"); + i.title="Click to display parse tree" + i.other=server.current_grammar_url + +"?command=parsetree&from="+lang+"&tree="+encodeURIComponent(abs); + return i; +} diff --git a/src/www/minibar/pgf_offline.js b/src/www/minibar/pgf_offline.js new file mode 100644 index 000000000..cd2d40b1c --- /dev/null +++ b/src/www/minibar/pgf_offline.js @@ -0,0 +1,96 @@ +// Assumes that Services.js has been loaded + +function pgf_offline(options) { + var server = { + // State variables (private): + grammars_url: "", + grammar_list: ["Foods.pgf"], + + current_grammar_url: null, + pgf : null, + + // Methods: + switch_grammar: function(grammar_url,cont) { + //debug("switch_grammar "); + var new_grammar_url=this.grammars_url+grammar_url; + var self=this; + var update_pgf=function(pgfbinary) { + debug("Got "+new_grammar_url+", length=" + +pgfbinary.length+", parsing... "); + self.pgf = {v: Services_decodePGF.v({v:pgfbinary}) } + //debug("done") + self.current_grammar_url=new_grammar_url; + cont(); + } + ajax_http_get_binary(new_grammar_url,update_pgf); + }, + get_grammarlist: function(cont) { cont([this.grammar_list]); }, + + get_languages: function(cont) { + cont(fromJSValue(Services_grammar.v(this.pgf))) + }, + grammar_info: function(cont) { + cont(fromJSValue(Services_grammar.v(this.pgf))) + }, + + get_random: function(cont) { + alert("Random generation not supported yet in the offline version"); + }, + linearize: function(args,cont) { + cont(fromJSValue(Services_linearize.v(this.pgf)(v(args.tree))(v(args.to)))); + }, + complete: function(args,cont) { + cont(fromJSValue(Services_complete.v(this.pgf)(v(args.from))(v(args.input)))); + }, + parse: function(args,cont) { + cont(fromJSValue(Services_parse.v(this.pgf)(v(args.from))(v(args.input)))); + }, + translate: function(args,cont) { + cont(fromJSValue(Services_translate.v(this.pgf)(v(args.from))(v(args.input)))); + }, + translategroup: function(args,cont) { + cont(fromJSValue(Services_translategroup.v(this.pgf)(v(args.from))(v(args.input)))); + } + }; + for(var o in options) server[o]=options[o]; + return server; +}; + + + +// See https://developer.mozilla.org/En/XMLHttpRequest/Using_XMLHttpRequest +function ajax_http_get_binary(url,callback) { + var http=GetXmlHttpObject() + if (http==null) { + alert ("Browser does not support HTTP Request") + return + } + var statechange=function() { + if (http.readyState==4 || http.readyState=="complete") { + if(http.status==200) { + var buffer=http.mozResponseArrayBuffer; + if(buffer) callback(bufferToString(buffer)) // Gecko 2 (Firefox 4) + else callback(http.responseText); // other browsers + } + else alert("Request for "+url+" failed: " + +http.status+" "+http.statusText); + } + } + http.onreadystatechange=statechange; + http.open("GET",url,true); + http.overrideMimeType('text/plain; charset=x-user-defined'); + http.send(null); + //dump("http get "+url+"\n") + return http; +} + +function bufferToString(buffer) { + // This function converts to the current representation of ByteString, + // but it would be better to use binary buffers for ByteStrings as well. + debug("bufferToString"); + var u=new Uint8Array(buffer); + var a=new Array(u.length); + for(var i=0;i<u.length;i++) + a[i]=String.fromCharCode(u[i]); + return a.join(""); +} diff --git a/src/www/minibar/pgf_online.js b/src/www/minibar/pgf_online.js new file mode 100644 index 000000000..ac6620ee5 --- /dev/null +++ b/src/www/minibar/pgf_online.js @@ -0,0 +1,52 @@ + +/* --- Grammar access object ------------------------------------------------ */ + +function pgf_online(options) { + var server = { + // State variables (private): + grammars_url: "/grammars/", + grammar_list: null, + current_grammar_url: null, + + // Methods: + switch_grammar: function(grammar_url,cont) { + this.current_grammar_url=this.grammars_url+grammar_url; + if(cont) cont(); + }, + get_grammarlist: function(cont) { + http_get_json(this.grammars_url+"grammars.cgi",cont); + }, + pgf_call: function(cmd,args,cont) { + var url=this.current_grammar_url+"?command="+cmd+encodeArgs(args) + http_get_json(url,cont); + }, + + get_languages: function(cont) { this.pgf_call("grammar",{},cont); }, + grammar_info: function(cont) { this.pgf_call("grammar",{},cont); }, + + get_random: function(args,cont) { // cat, limit + args.random=Math.random(); // side effect!! + this.pgf_call("random",args,cont); + }, + linearize: function(args,cont) { // tree, to + this.pgf_call("linearize",args,cont); + }, + complete: function(args,cont) { // from, input, cat, limit + this.pgf_call("complete",args,cont); + }, + parse: function(args,cont) { // from, input, cat + this.pgf_call("parse",args,cont); + }, + translate: function(args,cont) { // from, input, cat, to + this.pgf_call("translate",args,cont); + }, + translategroup: function(args,cont) { // from, input, cat, to + this.pgf_call("translategroup",args,cont); + } + + }; + for(var o in options) server[o]=options[o]; + if(server.grammar_list && server.grammar_list.length>0) + server.switch_grammar(server.grammar_list[0]); + return server; +}
\ No newline at end of file diff --git a/src/www/minibar/phrasebook.html b/src/www/minibar/phrasebook.html new file mode 100644 index 000000000..78f5c353f --- /dev/null +++ b/src/www/minibar/phrasebook.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<head> +<title>Phrasebook</title> +<link rel=stylesheet type="text/css" href="minibar.css"> +<meta charset="UTF-8"> +<meta name = "viewport" content = "width = device-width"> +</head> + +<body> + +<div id=minibar></div> + +<hr> + +<small> + +Powered by <a href="http://www.grammaticalframework.org/">GF</a>, +see <a href="http://www.grammaticalframework.org/examples/phrasebook/doc-phrasebook.html">doc</a>. + +</small> + +<script type="text/JavaScript" src="support.js"></script> +<script type="text/JavaScript" src="minibar.js"></script> +<script type="text/JavaScript" src="minibar_input.js"></script> +<script type="text/JavaScript" src="minibar_translations.js"></script> +<script type="text/JavaScript" src="minibar_support.js"></script> +<script type="text/JavaScript" src="pgf_online.js"></script> + +<script type="text/JavaScript"> + +var online_options={ + // grammars_url: "http://www.grammaticalframework.org/grammars/", +//grammars_url: "http://tournesol.cs.chalmers.se:41296/grammars", +//grammars_url: "http://localhost:41296/grammars/", + grammar_list: ["Phrasebook.pgf"] // leave undefined to get list from server +} + +var server=pgf_online(online_options); + +var phrasebook_options={ + delete_button_text: "Del", + help_url: "http://www.grammaticalframework.org/examples/phrasebook/help-phrasebook.html", + feedback_url: "feedback.html", + default_source_language: "Eng" +} + +start_minibar(server,phrasebook_options) +</script> + +</body> + +</html> + + + diff --git a/src/www/minibar/saldotest.html b/src/www/minibar/saldotest.html new file mode 100644 index 000000000..179c42e4d --- /dev/null +++ b/src/www/minibar/saldotest.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> <head> +<title>Saldotest</title> +<link rel=stylesheet type="text/css" href="minibar.css"> +<script type="text/JavaScript" src="support.js"></script> +<script type="text/JavaScript" src="saldotest.js"></script> +<meta name = "viewport" content = "width = device-width"> +</head> + +<body onload="start_saldotest();start_saldospel()"> + +<h2>Vilket ord ska bort?</h2> +<div id=saldospel> +</div> + +<h2>Hel- och halvspöke</h2> +<div id=saldotest> +</div> + +<hr> +<small> +[Baserad på <a href="http://spraakbanken.gu.se/sal/ws/">SALDOs nättjänster</a>] +</small> +<small class=modtime> +HTML <!-- hhmts start --> Last modified: Thu May 27 14:02:42 CEST 2010 <!-- hhmts end --> +</small> +<address>TH <img src="http://www.altocumulus.org/~hallgren/online.cgi?icon" alt=""></address> +</body> + +</html> diff --git a/src/www/minibar/saldotest.js b/src/www/minibar/saldotest.js new file mode 100644 index 000000000..47cb95047 --- /dev/null +++ b/src/www/minibar/saldotest.js @@ -0,0 +1,340 @@ + +var Saldo_ws_url = "http://spraakbanken.gu.se/ws/saldo-ws/"; +//var Saldo_ff_url = Saldo_ws_url+"ff/json+remember_completions/"; +var Saldo_lid_url = Saldo_ws_url+"lid/json"; + +function saldo_ws(fn,fmt,arg,cont_name) { + jsonp(Saldo_ws_url+fn+"/"+fmt+(cont_name ? "+"+cont_name : "")+"/"+arg,""); +} + +function saldo_json(fn,arg,cont_name) { saldo_ws(fn,"json",arg,cont_name); } +function saldo_lid(arg,cont_name) { saldo_json("lid",arg,cont_name); } +function saldo_lid_rnd(cont_name) { saldo_lid("rnd?"+Math.random(),cont_name); } + +var ordlista=[]; +var current=""; + +function start_saldotest() { + appendChildren(element("saldotest"), + [button("Slumpa","random_word()"), + button("Rensa","clear_all()"), + button("⌫","delete_last()"), + //button("Ordlista","show_ordlista()"), + button("Visa tänkbara drag","show_moves()"), + button("Gör ett drag","make_a_move()"), + //button("Visa prefix","show_prefixes()"), + div_id("surface"), + div_id("words"), + div_id("translations")]) + var style0="min-height: 3ex; margin: 5px; padding: 5px;"; + element("surface").setAttribute("style",style0+"border: 3px dashed #e0e0e0;"); + element("words").setAttribute("style",style0+"border: 3px solid #e0e0e0;"); + clear_all(); +} + +function random_word() { + saldo_lid_rnd("show_random"); +} + +function show_random(lid) { + var lex=lid.lex; + reset_all(lex.substring(0,lex.indexOf('.'))); +} + +function clear_all() { reset_all(""); } + +function reset_all(s) { + current=s; + element("surface").innerHTML=s; + element("translations").innerHTML=""; + get_completions(); +} + +function delete_last() { + var len=current.length; + if(len>0) { + current=current.substring(0,len-1); + var s=element("surface"); + s.innerHTML=current; + element("translations").innerHTML=""; + get_completions(); + } +} + +function with_completions(s,cont) { + var c=ordlista[s]; + if(c && c.a) cont(c); + else { + //if(c) alert("c already has fields"+field_names(c)); + ordlista[s]={put: function(c) { ordlista[s]=c; cont(c); }}; + var url=Saldo_ws_url+"ff/json+ordlista[\""+s+"\"].put/"+encodeURIComponent(s); + jsonp(url,""); + } +} + +function get_completions() { + with_completions(current,show_completions); +} + +function word(s) { + //var w=span_class("word",text(s)); + //if(s==" ") w.innerHTML=" "; + //w.setAttribute("onclick",'extend_current("'+s+'")'); + //return w; + return button(s,'extend_current("'+s+'")'); +} + +function extend_current(s) { + current+=s; + element("words").innerHTML=""; + element("surface").innerHTML=current; + get_completions(); +} + +function show_completions(saldo_ff) { + var box=element("words"); + box.innerHTML=""; + //var c=saldo_ff.c.split(""); + var c=filter(allowed,saldo_ff.c); + sort(c); + for(var i=0;i<c.length;i++) { + var s=c[i]; + if(s!='-') + box.appendChild(word(s)); + } + show_translations(saldo_ff.a); +} + +function allowed(c) { + switch(c) { + case 'å': + case 'ä': + case 'ö': + case 'é': + case 'ü': + return true; + default: + return 'a'<=c && c<='z'; + } +} + +// ordklasser: mxc sxc (förekommer bara som prefix), +// *h (förekommer bara som suffix) +function ignore(msd) { + switch(msd) { + case "c": + case "ci": + case "cm": + case "seg": + case "sms": + return true; + default: + return false; + } +} + +function count_wordforms(a) { + var cnt=0; + for(var i=0;i<a.length;i++) + if(!ignore(a[i].msd)) cnt++; + return cnt; +} + +function pad(s) { + return s.length>0 ? " "+s : ""; +} + +function show_translations(a) { + var tr=element("translations"); + tr.innerHTML=""; + //if(!a) alert("a undefined in show_translations"); + if(count_wordforms(a)<1) { + tr.appendChild(p(text(a.length<1 ? "Detta är inte en giltig ordform" + : "Denna form förekommer bara i sammansättningar"))); + element("surface").setAttribute("class","invalid"); + } + else { + element("surface").setAttribute("class","valid"); + for(var i=0;i<a.length;i++) + if(!ignore(a[i].msd)) + tr.appendChild(p(text(a[i].gf+" ("+a[i].pos+pad(a[i].is)+", "+a[i].msd+")"))); + } +} + +function show_ordlista() { + var trans=element("translations"); + trans.innerHTML="Följande ord har slagits upp: "; + var apnd=function(el) { trans.appendChild(el) }; + for(var i in ordlista) { + apnd(empty("br")); + apnd(span_class(ordlista[i].a.length<1 ? "invalid" : "valid",text(" "+i))); + apnd(text(": "+(ordlista[i].ok!=null ? ordlista[i].ok.length : "?") + +"/"+(ordlista[i].allowed!=null ? ordlista[i].allowed.length : "?"))); + } +} + +function extend_ordlista(s,cs,cont) { + if(cs.length<1) cont(); + else { + var c=cs[0]; + var cs2=cs.substring(1); + with_completions(s+c,function(o){extend_ordlista(s,cs2,cont)}); + } +} + +function known_possible_moves(s,cont) { + var c=implode(sort(filter(allowed,ordlista[s].c))); + ordlista[s].allowed=c; + extend_ordlista(s,c,function() { + var ok=""; + for(var i=0;i<c.length;i++) { + var next=s+c[i]; + var ff=ordlista[next]; + //if(!ff.a) alert(show_props(ff,"ff")); + if(next.length<2 || count_wordforms(ff.a)<1) ok+=c[i]; + } + ordlista[s].ok=ok; + cont(ok); + } + ); +} + +function unknown_possible_moves(s,cont) { + with_completions(s,function(c){known_possible_moves(s,cont);}); +} + +function currently_possible_moves(cont) { + known_possible_moves(current,cont); +} + +function show_moves() { + var trans=element("translations"); + trans.innerHTML="Letar efter möjliga drag"; + currently_possible_moves(function(ok) { + trans.innerHTML="Tänkbara drag: "+ok; + winning_moves(trans,ok); + }); +} + +function winning_moves(trans,ok) { + var ws=map(function(c){return current+c;},ok); + mapc(unknown_possible_moves,ws,function(oks){ + var winning=""; + for(i=0;i<oks.length;i++) + if(oks[i].length<1) winning+=ok[i]; + trans.innerHTML+="<br>Vinnande drag: "+winning; + }); +} + +function make_a_move() { + currently_possible_moves(function(ok) { + if(ok.length<1) element("translations").innerHTML="Hittade inga möjliga drag!"; + else { + var i=Math.floor(Math.random()*ok.length); + extend_current(ok[i]); + } + } + ); +} + +function show_prefixes_of(trans,s) { + if(s.length>0) { + var p=s.substr(0,s.length-1); + with_completions(p,function(c) { + if(count_wordforms(c.a)>0) trans.innerHTML+="<br>"+p; + show_prefixes_of(trans,p); + }); + } +} + +function show_prefixes() { + var trans=element("translations"); + trans.innerHTML="Prefix av "+current+":"; + show_prefixes_of(trans,current); +} + +/* -------------------------------------------------------------------------- */ + +var spel={ antal_ord: 4, // antal närbesläktade ord att visa + antal_korrekta_svar: 0, + antal_felaktiga_svar: 0 + }; + +function start_saldospel() { + spel.hylla=div_id("hylla"); + spel.status=div_id("status"); + //element("saldospel").innerHTML="<span id=score></span>"; + appendChildren(element("saldospel"), + [spel.hylla,spel.status, + p(text("")), + button("Nya ord","spel0()"), + text(" "), + wrap("b",span_id("score"))]); + spel.score=element("score"); + show_score(); + spel0(); +} + +function spel0() { // Välj ord 1 + saldo_lid_rnd("spel1"); +} + +function spel1(lid) { // Slå upp md1 för ord 1 + spel.lid=lid; + saldo_json("md1",lid.lex,"spel2"); +} + +function spel2(md1) { // Kontrollera att det finns minst 4 ord i md1 för ord1 + if(md1.length<spel.antal_ord) spel0(); + else { + spel.md1=md1; + spel3(); + } +} + +function spel3() { // Välj ord 2 + saldo_lid_rnd("spel4"); +} + +function spel4(lid) { // Slå upp md1 för ord 2 + spel.lid2=lid; + saldo_json("md1",lid.lex,"spel5"); +} + +function spel5(md1) { // Kontrollera att ord 1 och ord 2 inte har något gemensamt + var ordlista1=map(wf,spel.md1); + var ord2=wf(spel.lid2.lex); + var ordlista2=map(wf,md1).concat(ord2); + if(overlaps(ordlista1,ordlista2)) spel3(); + else spel6(ordlista1,ord2); +} + +function spel6(ordlista1,ord2) { + spel.ord2=ord2; + var pos=Math.floor(Math.random()*spel.antal_ord); + var ordlista=shuffle(shuffle(ordlista1).slice(0,spel.antal_ord).concat(ord2)); + spel.hylla.innerHTML=""; + var lista=empty_class("div","space"); + for(var i=0;i<ordlista.length;i++) + lista.appendChild((button(ordlista[i],"spel7(this)"))); + spel.hylla.appendChild(lista); +} + +function spel7(btn) { + btn.disabled=true; + var ok=btn.value==spel.ord2; + //btn.setAttribute("class",ok ? "correct" : "incorrect"); + btn.setAttribute("style",ok ? "color: green" : "color: red"); + if(ok) spel.antal_korrekta_svar++; else spel.antal_felaktiga_svar++; + show_score(); + if(ok) spel0(); +} + +function show_score() { + spel.score.innerHTML=""+spel.antal_korrekta_svar+" rätt, " + +spel.antal_felaktiga_svar+" fel"; +} + +function wf(ord) { // word form, wf("band..1") == "band" + return ord.split(".",1)[0].split("_").join(" "); +} diff --git a/src/www/minibar/support.js b/src/www/minibar/support.js new file mode 100644 index 000000000..c65c1a389 --- /dev/null +++ b/src/www/minibar/support.js @@ -0,0 +1,300 @@ +/* --- Accessing document elements ------------------------------------------ */ + +function element(id) { + return document.getElementById(id); +} + +/* --- JavaScript tricks ---------------------------------------------------- */ + +// To be able to use object methods that refer to "this" as callbacks +// See section 3.3 of https://github.com/spencertipping/js-in-ten-minutes/raw/master/js-in-ten-minutes.pdf +function bind(f, this_value) { + return function () {return f.apply (this_value, arguments)}; +}; + +/* --- JSONP ---------------------------------------------------------------- */ + +// Inspired by the function jsonp from +// http://www.west-wind.com/Weblog/posts/107136.aspx +// See also http://niryariv.wordpress.com/2009/05/05/jsonp-quickly/ +// http://en.wikipedia.org/wiki/JSON#JSONP +function jsonp(url,callback) +{ + if (url.indexOf("?") > -1) + url += "&jsonp=" + else + url += "?jsonp=" + url += callback; + //url += "&" + new Date().getTime().toString(); // prevent caching + + var script = empty("script"); + script.setAttribute("src",url); + script.setAttribute("type","text/javascript"); + document.body.appendChild(script); +} + +var json = {next:0}; + +// Like jsonp, but instead of passing the name of the callback function, you +// pass the callback function directly, making it possible to use anonymous +// functions. +function jsonpf(url,callback) +{ + var name="callback"+(json.next++); + json[name]=function(x) { delete json[name]; callback(x); } + jsonp(url,"json."+name); +} + +/* --- AJAX ----------------------------------------------------------------- */ + +function GetXmlHttpObject(handler) +{ + var objXMLHttp=null + if (window.XMLHttpRequest) + { + // See http://www.w3.org/TR/XMLHttpRequest/ + // https://developer.mozilla.org/en/xmlhttprequest + objXMLHttp=new XMLHttpRequest() + } + else if (window.ActiveXObject) + { + objXMLHttp=new ActiveXObject("Microsoft.XMLHTTP") + } + return objXMLHttp +} + +function ajax_http(method,url,body,callback,errorcallback) { + var http=GetXmlHttpObject() + if (!http) { + var errortext="Browser does not support HTTP Request"; + if(errorcallback) errorcallback(errortext,500) + else alert(errortext) + } + else { + var statechange=function() { + if (http.readyState==4 || http.readyState=="complete") { + if(http.status<300) callback(http.responseText,http.status); + else if(errorcallback) errorcallback(http.responseText,http.status); + else alert("Request for "+url+" failed: " + +http.status+" "+http.statusText); + } + } + http.onreadystatechange=statechange; + http.open(method,url,true) + http.send(body) + } + return http +} + +function ajax_http_get(url,callback,errorcallback) { + ajax_http("GET",url,null,callback,errorcallback) +} + +function ajax_http_post(url,formdata,callback,errorcallback) { + ajax_http("POST",url,formdata,callback,errorcallback) + // See https://developer.mozilla.org/En/XMLHttpRequest/Using_XMLHttpRequest#Using_FormData_objects +} + +// JSON via AJAX +function ajax_http_get_json(url,cont) { + ajax_http_get(url,function(txt) { cont(eval("("+txt+")")); }); +} + +function sameOrigin(url) { + var a=empty("a"); + a.href=url; // converts to an absolute URL + return hasPrefix(a.href,location.protocol+"//"+location.host+"/"); +} + +// Use AJAX when possible, fallback to JSONP +function http_get_json(url,cont) { + if(sameOrigin(url)) ajax_http_get_json(url,cont); + else jsonpf(url,cont); +} + +/* --- URL construction ----------------------------------------------------- */ + +function encodeArgs(args) { + var q="" + for(var arg in args) + if(args[arg]!=undefined) + q+="&"+arg+"="+encodeURIComponent(args[arg]); + return q; +} + +/* --- HTML construction ---------------------------------------------------- */ +function text(s) { return document.createTextNode(s); } + +function node(tag,as,ds) { + var n=document.createElement(tag); + for(var a in as) n.setAttribute(a,as[a]); + for(var i in ds) n.appendChild(ds[i]); + return n; +} + +function empty(tag,name,value) { + var el=node(tag,{},[]) + if(name && value) el.setAttribute(name,value); + return el; +} + +function empty_id(tag,id) { return empty(tag,"id",id); } +function empty_class(tag,cls) { return empty(tag,"class",cls); } + +function div_id(id) { return empty_id("div",id); } +function span_id(id) { return empty_id("span",id); } + +function wrap(tag,contents) { return node(tag,{},[contents]); } + +function wrap_class(tag,cls,contents) { + var el=empty_class(tag,cls); + if(contents) el.appendChild(contents); + return el; +} + +function span_class(cls,contents) { return wrap_class("span",cls,contents); } +function div_class(cls,contents) { return wrap_class("div",cls,contents); } + +function p(contents) { return wrap("p",contents); } +function dt(contents) { return wrap("dt",contents); } +function li(contents) { return wrap("li",contents); } + +function th(contents) { return wrap("th",contents); } +function td(contents) { return wrap("td",contents); } + +function tr(cells) { return node("tr",{},cells); } + +function button(label,action,key) { + var el=node("input",{"type":"button","value":label},[]); + if(typeof action=="string") el.setAttribute("onclick",action); + else el.onclick=action; + if(key) el.setAttribute("accesskey",key); + return el; +} + +function option(label,value) { + return node("option",{"value":value},[text(label)]); +} + +function appendChildren(el,ds) { + for(var i in ds) el.appendChild(ds[i]); + return el; +} + +function insertFirst(parent,child) { + parent.insertBefore(child,parent.firstChild); +} + +function tda(cs) { return node("td",{},cs); } + +function img(src) { return empty("img","src",src); } + +/* --- Debug ---------------------------------------------------------------- */ + +function debug(s) { + var d=element("debug"); + if(d) d.appendChild(text(s+"\n")) +} + +function show_props(obj, objName) { + var result = ""; + for (var i in obj) { + result += objName + "." + i + " = " + obj[i] + "<br>"; + } + return result; +} + +function field_names(obj) { + var result = ""; + for (var i in obj) { + result += " " + i; + } + return result; +} + +/* --- Data manipulation ---------------------------------------------------- */ +function swap(a,i,j) { // Note: this doesn't work on strings. + var tmp=a[i]; + a[i]=a[j]; + a[j]=tmp; + return a; +} + +function sort(a) { +// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/sort + return a.sort(); + /* // Note: this doesn't work on strings. + for(var i=0;i<a.length-1;i++) { + var min=i; + for(var j=i+1;j<a.length;j++) + if(a[j]<a[min]) min=j; + if(min!=i) swap(a,i,min); + } + return a; + */ +} + +function filter(p,xs) { + var ys=[]; + for(var i=0;i<xs.length;i++) + if(p(xs[i])) ys[ys.length]=xs[i]; + return ys; +} + +function implode(cs) { // array of strings to string + /* + var s=""; + for(var i=0;i<cs.length;i++) + s+=cs[i]; + return s; + */ + return cs.join(""); +} + +function hasPrefix(s,pre) { return s.substr(0,pre.length)==pre; } + +function commonPrefix(s1,s2) { + for(var i=0;i<s1.length && i<s2.length && s1[i]==s2[i];i++); + return s1.substr(0,i); +} + +/* +function all(p,xs) { + for(var i=0;i<xs.length;i++) + if(!p(xs[i])) return false; + return true; +} +*/ + +function map(f,xs) { + var ys=[]; + for(var i=0;i<xs.length;i++) ys[i]=f(xs[i]); + return ys; +} + +// map in continuation passing style +function mapc(f,xs,cont) { mapc_from(f,xs,0,[],cont); } + +function mapc_from(f,xs,i,ys,cont) { + if(i<xs.length) + f(xs[i],function(y){ys[i]=y;mapc_from(f,xs,i+1,ys,cont)}); + else + cont(ys); +} + +function overlaps(as,bs) { + for(var i=0;i<as.length;i++) + if(elem(as[i],bs)) return true; + return false; +} + +function elem(a,as) { + for(var i=0;i<as.length;i++) + if(a==as[i]) return true; + return false; +} + +function shuffle(a) { + for(i=0;i<a.length;i++) swap(a,i,Math.floor(Math.random()*a.length)) + return a; +} diff --git a/src/www/minibar/tree-btn.png b/src/www/minibar/tree-btn.png Binary files differnew file mode 100644 index 000000000..ebd243617 --- /dev/null +++ b/src/www/minibar/tree-btn.png |
