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/minibar | |
| 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/minibar')
22 files changed, 2369 insertions, 0 deletions
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 |
