Anony-bot 30 June 2010
A little robot using the Robots V2 API to expose a wave to a URL. If the URL is shared, then anyone can post anonymously on the wave.
A little robot using the Robots V2 API to expose a wave to a URL. If the URL is shared, then anyone can post anonymously on the wave.
Edit mode is no longer experimental, a new implementation includes a tiny diff engine which allows editing a post without necessarily destroying layout. Root blip editing is now possible. There is a new tag list on the bottom of each wave, also including an “Add Tag” button. Search results are now formatted with modification date, number of blips, number of unread blips and read/unread state. There is a new settings panel when you click the logo. Added support for the internet exploder browser starting at version six. Owner_utils is a setting which adds utilities like “set everyone as read only”. The New Wave feature no longer creates pop-up prompts, but rather silently creates and opens an empty wave. It renders the live-editing cursors. There is a new multipane interface for desktop. Gadget support has greatly improved. It handles rotation on a mobile webkit device better. It now uses Wave Data/Robots Protocol 0.22 and renders using the newly exposed conversation model.
So I guess it’s done, the DNS may still be propagating, but it should be not too bumpy.
Can you tell that the screenshot on the left is actually an embedded wave? Probably not.
First, a little overview on what this is. A lot of people are interested in using Google Wave as a commenting system, however the official embed api at the current state suffers from several shortfalls. It’s tedious to operate a wave-integrated commenting system. As an author, you have to manually create a wave for every post, copy the content over to the wave, get the wave ID, and paste it into your template. Every time you change your post, you have to yet again manually sync the page with wave. Once you’ve gotten the wave into the page, the embedded wave itself has a few issues. You can’t style it using CSS, and you can only change the background color and font. The width can’t adjust to the width of your page after the initial load. There’s no way to get rid of or change the style of the huge blue border around it. It forces it’s own scroll bar which looks nothing like anything else on the page and prevents intuitive navigation of comments using the same scrollbar as the rest of the page. Lastly, there’s no way for anonymous readers to comment on the post.
For each of these issues, the new w2_embed api offers a solution. Instead of using iframes to hack another page onto the current one, this one is built into the page, injecting elements directly onto the page’s DOM. These elements can be styled in nearly any imaginable way through CSS and HTML. The sandboxed and namespaced single-function API allows for multiple waves on one page with less than 10KB of JS overhead (Compare to about 1.4 megabytes). Instead of relying on a special Wave ID, the app uses a centralized and remotely stored database which associates any identifier you provide with functional wave IDs. That means, that instead of using a string like googlewave.com!w+Blsm0RMW_A
, you have the URL of your blog, say 2010/06/wave-embed-api
. Not only that, but it can automatically pull the title of your wave, the authors, the permalink and tags from the post, automatically create a wave from that with the same tags and title with the content automatically set as the root blip as well as a permalink back to the blog post. When viewed from wave, the user can follow, unfollow, search and find waves using the exact same familiar interface. On your blog, you can make the wave backend entirely seamless and subtle. Since it’s built on top of Anonybot, people can reply blips without having google wave accounts right from your blog.
It’s all part of a new embed API built upon Anony-bot (which itself is built upon microwave, which is built on wave reader, which is built upon the prototype python desktop client… Someone should make a wordpress plugin for this to make this family history even longer).
The last post noted the beginning of this project, and here is part two. The API has been cleaned up a lot. The code has been simplified. Features added and design changed.
The major change is a total decoupling of it and the mainline Anonybot codebase. There are some major implications of the change. In the original prototype of the API, it still had the iconic blip context menu inherited from microwave. That’s gone. So is the participants bar, link to real wave client, blip html, blip content, reply box, and everything. There are now no required or automatically added html/dom elements. Everything is specified through a simple HTML templating engine. Since the code isn’t shared, changes from each individual project now on probably won’t be instantly applied to both. This has advantages and disadvantages. On the plus side, is that changes to anonybot won’t break the embed api. However, bug fixes on one may not propagate to the other. The code can be much smaller since features like search, which was previously hidden, can now be totally removed. Also, since it’s been rewritten from scratch as an embed api, it no longer exposes any extra globals and multiple embeds can be done for each page.
The biggest change is the templating engine. Before, it only worked with the blip design, but now it’s totally integral to every aspect of the interface. Lets walk through the basics of the API, starting with a minimal example.
All the content is wrapped in a huge hidden `div`, and inside is one element with the `wavebody` class. That huge wave is the scope of the templating engine. That means that if you put `{{1+1}}` anywhere inside the `div`, it’ll show up as 2. Place it anywhere outside the div, and it shows up as `{{1+1}}`. Simple, right? The `wavebody` element is where the actual blips will go. But since there aren’t any blips beforehand, `wavebody` is filled with the template for a single blip. The content gets multiplied several times and that makes the wave. The template scheme is really simple. Basically, you just wrap arbitrary JS code within `{{` and `}}`. The code gets executed (in the scope of the wave) and the block gets replaced with the return value of the script. When you’re inside `wavebody` there are a few additional blip-specific things that you can add. Now we have to learn the magical snippets of code you can use inside the curly brackets. One of the objects exposed to the templates is `wave`. The nice thing is that it mirrors [the wave api](http://code.google.com/apis/wave/extensions/robots/protocol.html#Wavelets). So the wave object exposes the following attributes (copied from the google page). * `creationTime` denotes the [Unix time](http://en.wikipedia.org/wiki/Unix_time) at which this wavelet was created. * `creator` denotes the address of the participant who created this wavelet. * `lastModifiedTime` denotes the [Unix time](http://en.wikipedia.org/wiki/Unix_time) at which the wavelet was last modified by any participant. * `participants` contains an array of participant IDs for all participants on the wave. * `rootBlipId` contains the Blip ID of the root blip. * `title` contains the title of the wavelet, which by default consists of the first line of text up to the first carriage return. * `version` contains the version of this wavelet. Each atomic operation on a wavelet increases this version number. * `waveId` contains the Wave ID of this wavelet. * `waveletId` contains the wavelet ID of this wavelet. Note that for waves which contain only one wavelet (that don’t have private conversations, in other words), this wavelet ID is usually of the form `conv+root` indicating that the wavelet is identical to the conversation root, the root wave. * `dataDocuments` contains a dictionary (associated array) of the IDs and data of any data documents attached to this wavelet. In addition, there are a few non-standard components. * `permalink` contains a link to the waveref which points to the specific wave on the real wave client * `blips` contains a js object (similar to a hash or dictionary) of blipIDs associated to blip objects * `bliparr` contains an array of blip ids in no particular order, most frequently used for getting the number of replies in a conversation in `{{wave.bliparr.length-1}}` There are also some functions you can call. For example, `wave.creationTime` is in the form of a unix timestamp. That probably isn’t too useful to an ordinary user, so a `format_time` function is there to format it like “6/30 10:45am”. If you want to have some other custom datetime format, get me a script to do it in javascript in less than 500 bytes and I’ll include it in the next version. * `format_time(int)` Format a unix-style date as a human-readable “6/30 10:45am” or if you prefer “m/d h:MMtt” * `render(blip)` Render a blip and return the HTML. Most of the time, it’s unnecessary as there’s a blip.html attribute There are a few actions that you can do. Certain strings like `{{addparticipant}}` can be used in `onclick=` attributes to trigger certain actions on various events.Here you see a link and a button. The link has been configured to prompt the user for a participant ID to add when clicked, and the button was set to prompt the user for a tag to add when clicked. There are several such actions.
addparticipant
Prompt the suer for a wave address to add to the wavereload
Reload the current waveaddtag
Prompt the user for a tag to addsetname
Prompt the user to set a username for posting
There is also the user list. It comes in two flavors, {{participantlist}}
and {{contributorlist}}
. The latter only works when inside a blip scope (the stuff inside wavebody
). They’re special because they’re actually interactive. When the list is long, it shortens it and creates a link to expand.
participantlist
A list made from wave.participants
contributorlist
Only works when in wavebody
, a simple users list made from blip.contributors
Now, is finally the stuff that goes in the blip. Like wave
it extends some wave api features, so I’ll start with pasting what google has documented.
blipId
contains the ID of blip in which the event occurred.
childBlipIds
contains an array of blip IDs for each of the blip’s children.contributors
denotes participants who have contributed to the state of this blip.creator
denotes the participant who created this blip.lastModifiedTime
denotes the Unix time at which this blip was last modified by any participant.content
contains the textual content of this blip.version
contains the version of this blip. Each atomic operation on a blip increases this version number.waveId
contains the Wave ID associated with this blip.waveletId
contains the wavelet ID associated with this blip. Note that for waves which contain only one wavelet (that don’t have private conversations, in other words), this wavelet ID is usually of the form conv+root
indicating that the wavelet is identical to the conversation root, the root wave.
There are a few additional methods which, again are non-standard.
permalink
contains a link to the waveref which points to the specific blip on the real wave client
html
contains a string of the rendered (with formatting and annotations applied) content
There are also actions, similar to the global addparticipant
, reload
and setname
actions. They can still be used within wavebody
but there are some actions which can only work in the blip template.
edit
Add an edit box immediately below the blip, pre-populated with the text content of the blip
remove
Remove the blip, it will first issue a confirmation prompt to the userreply
Add a reply box immediately below the blip. If there is already a blip below, the reply box is indented.
Below is a little example showing how to use the blip
object.
Lastly is the JS part. Without it, nothing would ever show up. You need to include the currently 6.5KB JS library from http://anony-bot.appspot.com/assets/embed2.mini.js
. After that, you need a separate script tag anywhere after the big hidden super element. It must be outside the super element (unimaginable evils will happen if it isn’t). Inside that script tag needs to be a function call to w2_embed
. The first (and only) argument has to be a JS object with at least the element
key pointing to a DOM element which contains an element with the class wavebody
. There are lots of other config options, and here they are.
element
(Required) a dom element which contains a div with the “wavebody” class
identifier
The identifier for the wave that the server will use to generate a wave ID from, default: location.pathnameroot_title
If the identifier has not previously been associated with a wave ID, the wave will be created with the specified title. default: (no title)root_content
If the identifier has not previously been associated with a wave ID, the wave will be created with the specified content as the root blip. default: (no content)participants
The participants the wave should be created with if not previously associated with a wave ID, default: [], suggested: [‘public@a.gwave.com’]hideroot
Boolean whether or not to not render the root blipedit
A reference to a DOM element which can be templated into the edit box, should contain handlers for the actions {{submit}} and {{cancel}} as well as a textarea with the class “wavetext”tags
Set of tags to be added to the wave if the identifier has not previously been associated with a wave and must be created.api_root
The domain containing the server component (proxy and dictionary server). Default: anony-bot.appspot.comgadgets
Enable native gadgets (does not work)render_state
Display the user a gadget statechronological
Render blips chronologically (see Microwave’s “Classic Forum Layout” option)json_url
Location of JSON implementation to be loaded dynamically in case it’s not native to the browser default: http://anony-bot.appspot.com/assets/json2.mini.jswaveid
Skip the identifier lookup and directly reference a wave by it’s id You can see that the function call was saved to a variable named demo
. That’s useful because you can use that to reference later to manipulate things. The return of a w2_embed call is a JS object with the following keys.
config
A reference to the initial config objectreply(text)
Create a root-level replyreload()
Reload the wave.
So lets try putting everything together.
Afterthoughts: Just a little thing to add at the end. You can can create a a div with the class waveedit
as used above. It’s not strictly necessary, but it lets you customize things a bit more. You also need a CSS style for .wavethread with a padding-left (or margin-left) in order for threads to show up properly. Blips are automatically given a .waveblip class.
That screenshot on the top of the post, if you want to try it out. It’s here.
I’m not making any money off of this, so there’s no reason for me to hide the fallibilities of this solution from anyone. This, like any other solution is not perfect. There is no true wave-style real-time editing, it uses polling. It does however update every 10 seconds if it’s configured to do so using the poll_updates: true
configuration option. It can’t ever really do much better, since app engine doesn’t yet support pushing out real time data to attached clients, even though they announced the neat Channel API (still waiting to use it!) at I/O. Either way, that probably won’t help too much as w2_embed uses JSONP for client/server communication across multiple domains, and it’s unlikely that the channel api will allow for that. Also, since there’s no way to fetch a single blip, the user must request the entire wave to check the tiny 32 bit version integer if it’s been incremented, and if it has, then the whole wave has to be fetched again. It’s by no means efficient, and so my server would be very very sad if the polling interval was significantly increased. It’s not very SEO friendly. Though it’s not always that people want the comments to be indexed by search engines, so some people may care less. Accessibility-wise, it shouldn’t be too horrible if the screen reader/browser supports javascript and hugely depends on the HTML being used in conjunction with the script.
As of date, this was my longest blog post ever, and I would be sad if nobody commented (ironically using my non-wave-based commenting system). And I’m not posting my source code unless someone comments, so, an incentive :P
Every year at about this time, I switch hosts, now I’m switching to Host Monster. This site may probably be down for the next day or so.
http://antimatter15.com/ajaxanimator/webstore.crx
If you run chrome —enable-apps and install it, you might be able to see an icon and magically be able to use the app offline. Or wait until the webstore is finally released and then you can just install it.
Edit July 10, 2010: Actually it was a while ago, but now the app works again and automatically restores the last state using localstorage.
One of the most horrible developments in usability has been Pagination, because someone thought it would be a great idea to split everything into annoying separate pages. Sure it was a way to get around a technical issue of browser reflow while progressively rendering and bandwidth usage. But it set a nasty precedent for the future of web-interaction.
Now we have to live with it.
So I was thinking, is there any way to use my side buttons in Javascript? I quickly prototyped a demo but to my dismay, clicks only triggered with a .button of 0, 1 or 2. Then I realized that I could programatically alter history and detect hash changes. So then came another prototype which worked by setting the URL to “#Math.random()” and then doing a history.go(-1), hooking onto window.onhashchange to see when something changed. Then I discovered the new HTML5 history.pushState() and onpopstate events and switched accordingly.
Now, that it’s possible to trick the browser into populating the future of history. How do you decide what the user is probably going to next? The great thing is that if we guess wrong, it doesn’t really matter, anything is still better than a non-functional forward button.
So how do you predict where the user wants to go next? In most paginated systems, on the top and/or bottom of a post is a series of links [1] [2] [3] [4]. The current page is usually grayed out and unclickable. This makes it pretty easy to detect, just iterate each link by node order, parseInt the innerText and see if there’s a single-number jump in the middle, offset it by one and then you get the next page in order.
I actually wrote this such a long time ago that I basically forgot where the extension is and after using it, I’ve disabled it myself because chrome seems to have a few nasty bugs that cause it to crash (tabs at least) quite frequently when it’s enabled. But here it is.
One thing the ajax animator’s pretty bad at is stick figures. Sure it’s not impossible, but it can’t really compare with the ol-fashioned frame-by-frame joint-manipulation likeness of Pivot. It’s called stick2 because the original experiment with stickfigures was named stick.html, and when I went to extend it and didn’t feel like setting up a git/svn repo, I copied the file and named it stick2.html, and with no good project-naming skills, it stayed that way.
Anyway, this was a project that got pretty close to completion in early march, but I never bothered to blog about it until now. It should work pretty not-bad on an iPad J(except the color picker), though honestly, I’ve never tried it.
The interface is pure jQuery/html/css. The graphics are done with Raphael, but the player actually uses <canvas> for no particular reason.
Basically, it’s organized into two panels, the left-side figures-box and the bottom timeline. The figures-box contains figures (amazing!) and clicking on them adds them to your canvas. The two defaults are the pivot-style stickman, and something called “blank” which is a root node with no additional nodes. Though it shows up as a orange dot, unless you add something to it, it doesn’t have any actual look when viewed in the player.
On top is the context-sensitive buttons. Well the buttons in my screenshot aren’t context sensitive, they’re permanent. But when you click on a node, a new set of buttons (and words too!) appears. One is a line and the other is a circle. Click them to add a new segment or circle to the currently selected node. Then are various settings for the current segment (each node other than the root one is associated with a segment). Clicking those allows you to modify them. Also, a red X appears on the right, and that basically means remove the node and the child nodes.
So, now you have some extra nodes, how do you change them? Simply hold it down and drag, and the the segments move as well. But note that the length of the segment doesn’t change as you move it. That’s because by default, it locks the length of the segments. There are two ways to get around it. The first is to hold shift while dragging. The second is to tap the little lock icon on the top left.
On the bottom, is the timeline with live-previews of your frames with a semitransparent gray backdrop of numbers. Switch between each one by clicking on them and add one at the end by hitting the green “Add new frame” button.
On the canvas, there are two yellow squares, those allow you to resize the canvas.
On the very left of the top toolbar, is the play button. Hit it and the figures toolbox minimizes and it plays out your animation. Click it again to get back. Then is a little upload button. Hit it and then a little box pops up with a link to where you can find your animation in a way that you can share and to edit (not actually edit, but more like fork, as each save is given a unique id). Next is the download button which you hit, and get prompted by a big prompt-box which you use to paste in the ID of the animation you (or someone else) has saved, so you can edit it. Most of the time that’s useless as when you send a link with the player, it has a button which says “Edit”.
Sample animation: http://antimatter15.com/ajaxanimator/stick2/player.html?rlsm4lx14c
Try the application out: http://antimatter15.com/ajaxanimator/stick2/stick2.html
This isn’t really new, but I just remembered to write a post about it. Ajax Animator for iPad got a relatively minor, but certainly pretty important update.
The new update incorporates the TouchScroll javascript library to have nice flick-to-scroll-ness throughout native iPad apps.
The VectorEditor core had a few bug fixes, rotation now works and so does resizing (though it’s still susceptible to the always-existent resizing of rotated objects awkwardness - I would love it for someone to fix it). However, resizing doesn’t change the size of the bounding box, which is a somewhat awkward aesthetic but it’s still functional.
Here’s my foray into the flash-esque html5 game arena. It’s a simple game built initially in <canvas> but later scrapped for Raphael because I guess it’s something more suitable for svg than canvas. The interface is fairly simple, you click to start the game, where your projectile is sent off at the velocity relative to green blob in the center. Once it’s launched, the projectile is affected by the gravitational field of all the planets in some fairly pretty near-orbits. Once the projectile is in motion, clicking drops a new planet at where your cursor is, holding down makes the planet grow. The objective is to have the projectile not accelerate off the screen.
As per Kepler’s laws, getting near a planet produces the “gravitational slingshot” effect. Since the projectile tends to fly toward the center of planets, a magical divide-by-zero causes the infinite acceleration toward doom.
As with several other of my recent projects, it supports various configuration options via the url query string. If you don’t know how it works, basically, you append ?opt1=val1&opt2… to the url. Example: gravity2.html?grav=4 , simply gravity2.html?fastest or a combo of gravity2.html?fastest&grav=4&random. The current options are fastest to prevent the targeting of 80fps (accepts no args), target the target fps (obviously can not be used with fastest, in the case, fastest takes precedence) and it accepts one numerical argument, the default is 80. grav accepts one numerical argument, the default is 4 and is the attraction of the planets (zero isn’t very fun). _random _makes the planets start off in random places rather than the predefined magical positioning.
Feel free to post highscores in the comments.
Over a few days, things can change fairly quickly. There have been several speed improvements, a new Forum-Style blip rendering option which arranges blip linearly by the time edited with each containing a formatted quote of the parent to establish context. Attachments are now fully supported, including thumbnails and download links. The operations engine was totally rewritten which uses asynchronous XMLHttpRequest, a new callback based system and support for a batch operations (which means fewer requests and faster responses). A wavelet header containing a list of all participants in the entire wave has been added, as well as an Add Participant button. A specialized, extremely fast gadget viewer was added, which allows for blazingly fast rendering of two popular gadgets (and more will come), it works by bypassing the entire gadget infrastructure and loading trusted code directly inline with the DOM. There is a “New Wave” button which allows people to create new waves directly from the client. The OAuth backend was authenticated with google, for more secure login transactions. Blips have a new context menu which allows for features such as Delete Blip, Edit Blip and Change Title. A full changelog can be found here.
For no real reason, I was reading the Wikipedia article on Digital Steganography and saw the interesting image where an image of a kitty is extracted from some boring trees. I decided to port the example to <canvas>.