Re: [Snowy] Very basic HTML5 app to talk to the Snowy API



This is ridiculously cool.  Stuart, you are a madman!

We need to take a look at enabling some other auth methods in piston
besides OAuth so we can support this.  Also, I noticed the hard-coded
URLs are U1-specific.  According to the spec, all URLs should be
derived initially from the root URL.  I've retconned the wiki to
mention this:

http://live.gnome.org/Tomboy/Synchronization/REST

Anyone who'd like to work on merging this into Snowy (along with our
XSL to get pretty output) would get mad props!

Living in the future,
Sandy

On Sat, Apr 17, 2010 at 8:30 PM, Stuart Langridge
<stuart langridge canonical com> wrote:
> So, since Snowy's API is all HTTP, and HTML5 apps are the new hawt, it
> ought to be possible to combine the two.
>
> Bit of background, here. It is possible to build an HTML page which can
> appear as an application on a smartphone, like Androids or iPhones. It's
> also possible to build an HTML page which is cached by the browser on
> these phones, so that it works without actually hitting the internet.
> It's *also* possible for HTML pages to store data locally. At that
> point, that "HTML page" is basically an application, no? Mobile Safari
> on the iPhone has an "add to front screen" button, which "installs" the
> app on your iPhone; Android lets you bookmark it and then add the
> bookmark to your front screen. All this lot, put together, is called
> "HTML5 apps", and it's pretty cool.
>
> So, back to the point. It oughta therefore be possible to build an HTML5
> app which talks to the Snowy HTTP API to get your notes, stores them in
> its local storage database, and works offline. So, it does roughly what
> Tomdroid does.
>
> Below is some HTML that does this.
>
> Be told: it is very basic at the moment. Specifically, it probably
> doesn't implement the sync algorithm quite right, and importantly it
> doesn't let you edit notes; just view them. However, it should be a
> useful basis for doing more of this inside Snowy.
>
> HTML page below, which is a straight cut-and-paste from my local code
> and will therefore need tweaking. It also requires iUI, available from
> http://code.google.com/p/iui/. See the end for some notes, which are
> important.
>
> ------------------8<---------------------------
> <!DOCTYPE HTML>
> <html manifest="html5app.manifest">
> <head>
>    <title>Ubuntu One: Notes</title>
>    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
>    <meta name="viewport" content="width=device-width;
> initial-scale=1.0; maximum-scale=1.0; user-scalable-0;">
>    <meta name="apple-touch-fullscreen" content="YES">
>    <link rel="shortcut icon" href="/media/img/fav.ico">
>    <link href="/media/apple-touch-icon.png" rel="apple-touch-icon">
>
> <style type="text/css" media="screen">@import "/media/iui/iui.css";</style>
>
>    <style type="text/css" media="screen">
>    .footer{padding:30px 0
> 10px;font-size:10px!important;color:#999;text-align:center;}
>    .footer p{margin-bottom:4px}
>    .footer p.plain a{color:inherit;}
>    </style>
>    <script type="application/x-javascript"
> src="/media/iui/iui.js"></script>
> </head>
> <body>
>
>
> <div class="toolbar">
>    <h1 id="pageTitle">Notes</h1>
>    <a id="backButton" class="button" href="#"></a>
>    <a class="button" href="#sync" id="syncbutton">Sync</a>
> </div>
> <ul id="home" selected="true">
> </ul>
> <div id="new">
> new note!
> </div>
> <div id="sync" class="panel">
> <h2>Synchronising notes with Ubuntu One</h2>
> <fieldset>
> </fieldset>
> </div>
> <script>
> iui.animOn = true;
>
> var u1notes = {
>    CLIENT_LATEST_SYNC_REVISION: "client-latest-sync-revision",
>    CLIENT_LATEST_CHANGE: "client-latest-change",
>    CLIENT_LATEST_SYNC_TIME: "client-latest-sync-time",
>
>    init: function() {
>        document.getElementById("syncbutton").onclick = u1notes.sync;
>        u1notes.currentlySyncing = false;
>        u1notes.populateNotesList();
>    },
>
>    getLocalNotes: function() {
>        var localnotes = [];
>        for (i=0; i<=localStorage.length-1; i++) {
>            key = localStorage.key(i);
>            if (key.match(/^guid-/)) {
>                localnotes.push(JSON.parse(localStorage.getItem(key)));
>            }
>        }
>        return localnotes;
>    },
>
>    populateNotesList: function() {
>        var notes = u1notes.getLocalNotes();
>        var hm = document.getElementById("home");
>        hm.innerHTML = "";
>        for (i=0; i<notes.length; i++) {
>            var note = notes[i];
>            var divid = "guid-" + note.guid;
>            var ndiv = document.getElementById(divid);
>            if (!ndiv) {
>                ndiv = document.createElement("div");
>                ndiv.id = divid;
>                document.body.appendChild(ndiv);
>            }
>            ndiv.innerHTML = note["note-content"];
>            var nli = document.createElement("li");
>            var nlia = document.createElement("a");
>            nlia.href = "#guid-" + note.guid;
>            nlia.appendChild(document.createTextNode(note.title))
>            nli.appendChild(nlia);
>            hm.appendChild(nli);
>        }
>    },
>
>    sync: function(e) {
>        if (!u1notes.currentlySyncing) {
>            u1notes.currentlySyncing = true;
>            document.querySelector("#sync fieldset").innerHTML = "";
>            u1notes.addSyncLine("Start", new Date());
>            u1notes.api_request("op/", function(basenotedata) {
>                var clientLatestSyncRevision =
> localStorage.getItem(u1notes.CLIENT_LATEST_SYNC_REVISION);
>                if (!clientLatestSyncRevision) clientLatestSyncRevision
> = -1;
>                var serverLatestSyncRevision =
> parseInt(basenotedata["latest-sync-revision"]);
>                var proposedSyncRevision = serverLatestSyncRevision;
>                var clientLatestChangeTime =
> parseInt(localStorage.getItem(u1notes.CLIENT_LATEST_CHANGE));
>                if (!clientLatestChangeTime) clientLatestChangeTime = 0;
>                var clientLatestSyncTime =
> parseInt(localStorage.getItem(u1notes.CLIENT_LATEST_SYNC_TIME));
>                if (!clientLatestSyncTime) clientLatestSyncTime = 0;
>                u1notes.addSyncLine("Revnos", "clientLatestSyncRevision:" +
>                    clientLatestSyncRevision + ",
> serverLatestSyncRevision:" +
>                    serverLatestSyncRevision);
>                if (serverLatestSyncRevision > clientLatestSyncRevision
> || clientLatestChangeTime > clientLatestSyncTime) {
>                    // something has changed on either server or client,
> so we need to sync
>
>                    // Get list of note updates since
> client.LastSyncRevision
>                    u1notes.api_request("op/?include_notes=true&since="
> + clientLatestSyncRevision, function(serverupdates) {
>
>                        // Next, process updates from the server:
>                        // Foreach new or updated note in the list of
> updates
>                        for (var i=0; i<serverupdates.notes.length; i++) {
>                            // Look for a local note with the same GUID
>                            var localguid = "guid-" +
> serverupdates.notes[i].guid;
>                            var localnote = localStorage.getItem(localguid);
>                            if (!localnote) {
>                                // If there is no note with the same
> GUID, this is a new note
>                                // Create a new local note from the update
>
> serverupdates.notes[i]["last-sync-revision"] = proposedSyncRevision;
>                                localStorage.setItem(localguid,
> JSON.stringify(serverupdates.notes[i]));
>                                u1notes.addSyncLine("New",
> serverupdates.notes[i].title);
>                            } else {
>                                // If there is a note with the same
> GUID, this is an updated note
>                                var note_change_time =
> parseInt(localnote["last-change-time"]);
>                                if (!note_change_time) note_change_time = 0;
>                                if (note_change_time <
> clientLatestSyncTime) {
>                                    // If the local note has not been
> modified since client.LastSyncDate
>                                    // apply the update from the server
>
> serverupdates.notes[i]["last-sync-revision"] = proposedSyncRevision;
>
> serverupdates.notes[i]["last-change-time"] = (new
> Date()).getTime().toString();
>                                    localStorage.setItem(localguid,
> JSON.stringify(serverupdates.notes[i]));
>                                    u1notes.addSyncLine("Received",
> serverupdates.notes[i].title);
>                                } else {
>                                    // If the local note *has* been
> modified since client.LastSyncDate
>                                    // there is a conflict that must be
> handled
>                                    u1notes.addSyncLine("Conflict",
> "Conflict on " + localnote.title);
>                                }
>                            }
>                        }
>                        u1notes.completeSync(proposedSyncRevision);
>                    });
>                } else {
>                    u1notes.addSyncLine("No change", "");
>                    u1notes.completeSync(proposedSyncRevision);
>                }
>            });
>        }
>    },
>
>    completeSync: function(proposedSyncRevision) {
>        var dt = new Date();
>        localStorage.setItem(u1notes.CLIENT_LATEST_SYNC_TIME,
> dt.getTime().toString());
>        u1notes.addSyncLine("Complete", new Date());
>        u1notes.currentlySyncing = false;
>        localStorage.setItem(u1notes.CLIENT_LATEST_SYNC_REVISION,
> proposedSyncRevision);
>        u1notes.populateNotesList();
>    },
>
>    addSyncLine: function(lbl, data) {
>        var fs = document.querySelector("#sync fieldset");
>        var d = document.createElement("div");
>        d.className = "row";
>        var l = document.createElement("label");
>        l.appendChild(document.createTextNode(lbl));
>        var s = document.createElement("span");
>        s.appendChild(document.createTextNode(data));
>        d.appendChild(l);
>        d.appendChild(s);
>        fs.appendChild(d);
>    },
>
>    api_request: function(url, cb) {
>        var rq_url = "api/1.0/" + url;
>        var xhr = new XMLHttpRequest();
>        xhr.open("GET", rq_url, true);
>        console.log(rq_url);
>        var aborttimer = setTimeout(function() {
>            xhr.abort();
>            u1notes.addSyncLine("Offline", "Couldn't contact Ubuntu One");
>            u1notes.currentlySyncing = false;
>        }, 5000);
>        xhr.onreadystatechange = function() {
>            if (xhr.readyState == 4) {
>                clearTimeout(aborttimer);
>                if (xhr.status != 200) {
>                    u1notes.addSyncLine("Error", "The Ubuntu One servers
> returned an error (" + xhr.status + ")");
>                    u1notes.currentlySyncing = false;
>                    return;
>                }
>                cb(JSON.parse(xhr.responseText));
>            }
>        }
>        xhr.send();
>    }
>
> }
> u1notes.init();
>
>
> </script>
> </body>
> </html>
> ------------------8<---------------------------
>
> Notes:
>
> You have to provide html5app.manifest. It's a text file which looks like
> this:
>
> ------------------8<---------------------------
> CACHE MANIFEST
> #version 1272748372
> /media/iui/iui.css
> /media/iui/iui.js
> /media/iui/toolbar.png
> /media/iui/toolButton.png
> /media/iui/backButton.png
> /media/iui/listArrow.png
> /media/iui/selection.png
> /media/iui/loading.gif
> /media/iui/blueButton.png
> /media/iui/listGroup.png
> /media/iui/listArrowSel.png
>
> NETWORK:
> /notes/api
> ------------------8<---------------------------
>
> and it *must* be served with mimetype text/cache-manifest or it won't work.
>
> It relies on being able to hit the Snowy API without authentication.
> Because it's served as *part* of Snowy, it should be able to do this,
> but if Snowy's configured to *only* allow OAuth to the API (and not
> cookie auth for logged-in users) then that'll need tweaking.
>
> Have fun.
>
> sil
> _______________________________________________
> snowy-list mailing list
> snowy-list gnome org
> http://mail.gnome.org/mailman/listinfo/snowy-list
>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]