var tryTemplate = document.getElementById('tryTemplate');
- function addToHistory(hash, imgUrl) {
- var clone = tryTemplate.content.cloneNode(true);
- clone.querySelector('a').href = '/c/' + hash;
- clone.querySelector('img').src = imgUrl;
- tryHistory.insertBefore(clone, tryHistory.firstChild);
- }
-
-
function beginWait() {
document.body.classList.add('waiting');
run.disabled = true;
/**
+ * Callback when there's an XHR error.
+ * @param e The callback event.
+ */
+ function xhrError(e) {
+ endWait();
+ alert('Something bad happened: ' + e);
+ }
+
+ /**
+ * Called when an image in the workspace history is clicked.
+ */
+ function historyClick() {
+ beginWait();
+ var req = new XMLHttpRequest();
+ req.addEventListener('load', historyComplete);
+ req.addEventListener('error', xhrError);
+ req.overrideMimeType('application/json');
+ req.open('GET', this.getAttribute('data-try'), true);
+ req.send();
+ }
+
+
+ /**
+ * Callback for when the XHR kicked off in historyClick() returns.
+ */
+ function historyComplete(e) {
+ // The response is JSON of the form:
+ // {
+ // "hash": "unique id for a try",
+ // "code": "source code for try"
+ // }
+ endWait();
+ body = JSON.parse(e.target.response);
+ code.value = body.code;
+ img.src = '/i/'+body.hash+'.png';
+ if (permalink) {
+ permalink.href = '/c/' + body.hash;
+ }
+ }
+
+
+ /**
+ * Add the given try image to the history of a workspace.
+ */
+ function addToHistory(hash, imgUrl) {
+ var clone = tryTemplate.content.cloneNode(true);
+ clone.querySelector('img').src = imgUrl;
+ clone.querySelector('.tries').setAttribute('data-try', '/json/' + hash);
+ tryHistory.insertBefore(clone, tryHistory.firstChild);
+ tryHistory.querySelector('.tries').addEventListener('click', historyClick, true);
+ }
+
+
+ /**
* Callback for when the XHR returns after attempting to run the code.
* @param e The callback event.
*/
if (tryHistory) {
addToHistory(body.hash, 'data:image/png;base64,' + body.img);
} else {
- window.history.pushState(null, null, "./" + body.hash);
+ window.history.pushState(null, null, './' + body.hash);
}
if (permalink) {
- permalink.href = "/c/" + body.hash;
+ permalink.href = '/c/' + body.hash;
}
if (embed) {
var url = document.URL;
}
- /**
- * Callback where there's an XHR error.
- * @param e The callback event.
- */
- function codeError(e) {
- endWait();
- alert('Something bad happened: ' + e);
- }
-
-
function onSubmitCode() {
beginWait();
var req = new XMLHttpRequest();
req.addEventListener('load', codeComplete);
- req.addEventListener('error', codeError);
+ req.addEventListener('error', xhrError);
req.overrideMimeType('application/json');
req.open('POST', '/', true);
req.setRequestHeader('content-type', 'application/json');
- req.send(JSON.stringify({"code": code.value, "name": workspaceName}));
+ req.send(JSON.stringify({'code': code.value, 'name': workspaceName}));
}
run.addEventListener('click', onSubmitCode);
<input type="text" value="" id="embed" readonly style="display:none;">
<p>
- <img id='img' src=''/>
+ <img id='img' src='{{if .Hash}}/i/{{.Hash}}.png{{end}}'/>
</p>
<pre><code id='output'></code></pre>
<link rel="stylesheet" href="/css/" type="text/css" media="screen">
</head>
<body>
-
<template id=tryTemplate>
- <div class=tries>
- <a href="">
- <img width=64 height=64 src="">
- </a>
- </div>
+ <div class=tries data-try="">
+ <img width=64 height=64 src="">
+ </div>
</template>
{{template "titlebar.html"}}
<section id=content>
</code></pre>
<input type='button' value='Run' id='run'>
+ <a href='{{if .Hash}}/c/{{.Hash}}{{end}}' target=_blank id=permalink>Share</a>
<p>Image appears here:</p>
- <img id='img' src=''/>
+ <img id='img' src='{{if .Hash}}/i/{{.Hash}}.png{{end}}'/>
<pre><code id='output'></code></pre>
</section>
// imageLink is the regex that matches URLs paths that are direct links to PNGs.
imageLink = regexp.MustCompile("^/i/([a-f0-9]+.png)$")
+ // tryInfoLink is the regex that matches URLs paths that are direct links to data about a single try.
+ tryInfoLink = regexp.MustCompile("^/json/([a-f0-9]+)$")
+
// workspaceLink is the regex that matches URLs paths for workspaces.
workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$")
type Workspace struct {
Name string
Code string
+ Hash string
Tries []Try
}
}
// getCode returns the code for a given hash, or the empty string if not found.
-func getCode(hash string) string {
+func getCode(hash string) (string, error) {
code := ""
if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan(&code); err != nil {
log.Printf("ERROR: Code for hash is missing: %q\n", err)
+ return code, err
}
- return code
+ return code, nil
}
func workspaceHandler(w http.ResponseWriter, r *http.Request) {
}
}
var code string
+ var hash string
if len(tries) == 0 {
code = DEFAULT_SAMPLE
} else {
- code = getCode(tries[len(tries)-1].Hash)
+ hash = tries[len(tries)-1].Hash
+ code, _ = getCode(hash)
}
- if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, Code: code, Name: name}); err != nil {
+ if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, Code: code, Name: name, Hash: hash}); err != nil {
log.Printf("ERROR: Failed to expand template: %q\n", err)
}
} else if r.Method == "POST" {
type TryRequest struct {
Code string `json:"code"`
- Name string `json:"name"`
+ Name string `json:"name"` // Optional name of the workspace the code is in.
}
// iframeHandler handles the GET and POST of the main page.
return
}
var code string
- // Load 'code' with the code found in the database.
- if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan(&code); err != nil {
+ code, err := getCode(hash)
+ if err != nil {
http.NotFound(w, r)
return
}
}
}
+type TryInfo struct {
+ Hash string `json:"hash"`
+ Code string `json:"code"`
+}
+
+// tryInfoHandler returns information about a specific try.
+func tryInfoHandler(w http.ResponseWriter, r *http.Request) {
+ log.Printf("Try Info Handler: %q\n", r.URL.Path)
+ if r.Method != "GET" {
+ http.NotFound(w, r)
+ return
+ }
+ match := tryInfoLink.FindStringSubmatch(r.URL.Path)
+ if len(match) != 2 {
+ http.NotFound(w, r)
+ return
+ }
+ hash := match[1]
+ code, err := getCode(hash)
+ if err != nil {
+ http.NotFound(w, r)
+ return
+ }
+ m := TryInfo{
+ Hash: hash,
+ Code: code,
+ }
+ resp, err := json.Marshal(m)
+ if err != nil {
+ reportError(w, r, err, "Failed to serialize a response.")
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(resp)
+}
+
// mainHandler handles the GET and POST of the main page.
func mainHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Main Handler: %q\n", r.URL.Path)
if r.Method == "GET" {
code := DEFAULT_SAMPLE
match := directLink.FindStringSubmatch(r.URL.Path)
+ var hash string
if len(match) == 2 && r.URL.Path != "/" {
- hash := match[1]
+ hash = match[1]
if db == nil {
http.NotFound(w, r)
return
}
}
// Expand the template.
- if err := indexTemplate.Execute(w, userCode{UserCode: code}); err != nil {
+ if err := indexTemplate.Execute(w, userCode{UserCode: code, Hash: hash}); err != nil {
log.Printf("ERROR: Failed to expand template: %q\n", err)
}
} else if r.Method == "POST" {
http.HandleFunc("/w/", workspaceHandler)
http.HandleFunc("/recent/", recentHandler)
http.HandleFunc("/iframe/", iframeHandler)
+ http.HandleFunc("/json/", tryInfoHandler)
http.HandleFunc("/css/", cssHandler)
http.HandleFunc("/js/", jsHandler)
+ // TODO Break out /c/ as it's own handler.
http.HandleFunc("/", mainHandler)
log.Fatal(http.ListenAndServe(*port, nil))
}