Things in Jars

Simon Madine (thingsinjars)

@thingsinjarsFollow me on Twitter

Hi, I’m Simon Madine and I make digital toys and write guides on web development.

I'm a senior web dev and evangelist for Nokia Maps in Berlin.

  • Twitter
  • Flickr
  • GitHub
  • Forrst
  • Shelvist
  • Last.FM
  • RSS
  • Open Source Ideas

    Written 30 Oct 2009

    Ideas

    All too often, I have ideas which might make a cool website or iPhone app or whatever and I know I just don't have the time to build them. I'm going to post them here in the hope that someone else might find a use for them. These ideas might already be in existence, of course. I'm not claiming they are unique in any way (although some might be).

    You are free to take these ideas and do whatever you like with them. Of course, if they become amazingly successful, I could do with a bigger TV...

    Comments

  • SpreadShop

    Written 9 Apr 2008

    Cartoons

    SpreadShop

    If you wake up every morning and think, "My t-shirts are so dull, I wish I had interesting clothes...", you need to have a look at my online shop.


    Comments

  • Uploaded from Twitter

    Written 28 Dec 2010

    Photos

    Uploaded from Twitter


    Flooding in the Meadows + seat in coffee shop = this sketch:

    Comments

  • Uploaded from Twitter

    Written 26 Dec 2010

    Photos

    Uploaded from Twitter


    A screenshot from one of my current JS side projects. A productive boxing day...

    Comments

  • Uncooked Composition 2

    Written 9 Dec 2010

    Not Geek

    Download the file

    Here's another short session of random piano noodling. As far as I can picture, this would be suitable for a montage in a film where the protagonist is mulling over the fact that his wife has left him and it takes him a while to get used to the idea but he finds solace in his dog. Or something like that.

    If the end seems a bit abrupt, it's because Jenni came in and reminded me we were actually supposed to have gone to the shops 10 minutes previous.

    Comments

  • Testing CSS3 stuff

    Written 8 Dec 2010

    Geek,CSS,Development

    You may have seen Google's 'Watch this space' advertising appearing all over the place. They have quite eye-catching diagonally striped backgrounds in various colours. A couple of days ago, I was wondering how easy it would be to recreate this in CSS without images. Unfortunately, the state of CSS 3 is such that some things work wonderfully, some just plain don't (scoped attribute, I'm looking at you). The following code relies on vendor extensions and so, unless you're willing to tend it and correct it after the spec is finalised, don't use this on a production server.

    The most obvious thing to notice from the following code, though, is the competing syntax for the repeating linear gradient style. Mozilla have separated it into a distinct style (-moz-repeating-linear-gradient) while Webkit have built it as an option to their general gradient style (-webkit-gradient).

    body {
    	background-image: -moz-repeating-linear-gradient(
    		-45deg,
    		transparent,
    		transparent       25%,
    		rgba(0,0,0,0.15)  25%,
    		rgba(0,0,0,0.15)  50%,
    		transparent       50%,
    		transparent       75%,
    		rgba(0,0,0,0.15)  75%,
    		rgba(0,0,0,0.15)
    	);
    	background-image: -webkit-gradient(
    		linear,
    		0% 0%,
    		100% 100%,
    		from(transparent),
    		color-stop(0.25, transparent),
    		color-stop(0.25, rgba(0,0,0,0.15)),
    		color-stop(0.50, rgba(0,0,0,0.15)),
    		color-stop(0.50, transparent),
    		color-stop(0.75, transparent),
    		color-stop(0.75, rgba(0,0,0,0.15)),
    		to(rgba(0,0,0,0.15))
    	);
    }

    To get a better idea of what this does, view source on this demo page. This includes a button to change the class on the body (using JS) which simply changes the background colour – the stripes are semi-transparent on top of that. Remember, due to the vendor prefixes, this only works in -moz or -webkit browsers.

    It's supposed to look like this:

    Comments

  • Tokyo Recommendations

    Written 6 Dec 2010

    Not Geek

    A friend recently asked me for some recommendations for what she could do on a trip to Tokyo. I pretty much always recommend the same things so I thought I'd write them up with maps and streetview and the like.

    Food

    If you're going out for food the best place I can recommend is Shin Hi no Moto (a.k.a. Andy's Izakaya). It's run by a friendly English guy (Andy) and his family. It gets really busy later on so you're best to phone and book on +81 3 3214 8021. It's okay, you can book in English. They do amazing sashimi platters and big mugs of beer. Just make sure you don't order rice (there's no rice in an izakaya and they might scowl at you if you do). You can get there by taking the Yamanote line to Yurakucho.

    • Andy's Shin Hi no Moto

    For general daily eats, I'm addicted to Yoshinoya. Especially their Gyuu-don. Tasty, healthy and cheap. You can find Yoshinoya everywhere.

    Walks

    West Central Tokyo

    If you're going to be there over a Sunday, you have to go to Harajuku. Even if you aren't there on a Sunday, the walk up Takeshita-dori is great fun. Here's a map of a little walk you can take up Takeshita-dori, round Harajuku and down to Shibuya:

    • Takeshita-Harajuku-Shibuya

    North-East Central Tokyo

    If you fancy some culture, try this route. It takes in the Imperial Palace, Sumo museum and Edo-Toyko museum. You can finish off in Akihabara for sheer geek awesome or save that for another day.

    • Imperial Palace-Edo-Tokyo Museum

    Tokyo bay/Odaiba

    Some people called me crazy for enjoying it but I like the walk across the rainbow bridge to Odaiba. Take the Yamanote to Tamachi and wander east-ish. You'll see the bridge once you're closer to the shore. You can take the lift up to the start of the walk and then wander out for some amazing views. It is, unfortunately, very noisy due to all the traffic but it's worth it. Head right across the bridge and follow it down, it'll probably take about an hour. Once you're on dry land again on Odaiba, you can wander around the shopping malls there (Aquacity, Seaside mall), take in the Statue of Liberty, go for a bite to eat and eventually head back. If you time it for getting dark, you can either get some amazing views of the bridge lit up at night or just enjoy the Yurikamome ride back (it's a completely automated train with no driver).

    • Rainbow Bridge-Odaiba-Yurikamome

    Views

    For the best view across Tokyo, the Tokyo Metropolitan Government building (a.k.a. TMG or Tocho) really can't be beaten (especially as it's free to go up). It's in Shinjuku.

    • Tokyo Metropolitan Government building

    Comments

  • Uncooked Composition

    Written 5 Dec 2010

    Not Geek

    Music, much like mathematics, is a young person's game. If you haven't made it by the time you're 25, your chances of making an impact on the world are significantly diminished. That's not to say it's impossible, it's just much less likely.

    Basically, I'm beginning to come to the realisation I'm not going to be a rock star. I might not even make it into space. To that end, I've decided that, instead of scribbling away at writing and rewriting the same songs I've been trying to improve for the last 10 years, I'd go the other way. A few months ago, I put a dictaphone next to the piano and started recording the occasional random improvisation. Originally, the idea had been to pick the best bits and rework them for some reason or another but after listening back to them, I found there's some appeal in just hearing the raw first-take complete with do-overs and occasional accidental 'quotes' from other pieces.

    Over the next little while, I'll be uploading some of them just so that I know I've done something with them other than leave them on a tape in the back of a drawer. When listening to them, bear in mind two things:

    1. The piano needs retuned almost every week so some of it might be a bit rough.
    2. This is, as the title says, 'Uncooked Composition'. I come in from work, take my shoes off, sit down at the piano, press record. There's no post-processing anywhere so there will be mistakes and do-overs.
    Download the file

    Comments

  • Uploaded from Twitter

    Written 13 Nov 2010

    Photos,Not Geek

    Uploaded from Twitter


    Me, @jennifermadine and Oskar making a dramatic film-noir escape from the maternity ward

    Comments

  • Uploaded from Twitter

    Written 30 Oct 2010

    Photos,Not Geek

    Uploaded from Twitter


    @jennifermadine Lookin' good on Arthur's seat.

    Comments

  • Uploaded from Twitter

    Written 27 Oct 2010

    Photos

    Uploaded from Twitter


    Decided my private URL shortener needed a shiny design even though it's only me that uses it.

    Comments

  • Uploaded from Twitter

    Written 26 Oct 2010

    Photos

    Uploaded from Twitter


    We have this unexplained locked box on the wall in our office.

    Comments

  • Uploaded from Twitter

    Written 26 Oct 2010

    Photos

    Uploaded from Twitter


    After some paperclip lockpicking, I find it contains a key on a chain. Any ideas?

    Comments

  • Uploaded from Twitter

    Written 25 Oct 2010

    Photos

    Uploaded from Twitter


    Uploaded from Twitter for iPhone

    Comments

  • Fake girl protects fake fish from fake cat

    Written 25 Oct 2010

    Photos

    I decided to make my desktop more dramatic...


    Originally uploaded by thingsinjars on flickr

    Comments

  • The Elementals

    Written 15 Oct 2010

    Geek,iOS,Design

    In one of my day jobs I do something involving education, large public institutions and web stuff and a while back I thought it might be an excellent idea to have a go at designing some cool educational toys. Learnin' 'n' Fun! At the same time! omg! And so forth!

    The idea was to build the kind of thing you could use to squeeze knowledge into people's heads without them complaining. Y'see, it's never a good thing to trick people into learning. If your educational toy/game/experience relies too much on hiding the information behind the fun then the big reveal at the end – "Ha, I tricked you into learning something!" – will leave the player feeling cheated and not change their attitude towards learning. If, on the other hand, you try and push the ideas you want to get across at the expense of the core game mechanic, you'll end up with a bored user. My opinion is that you've got to be up front about the learning. You've got to say to the user "Look, this is learning and it's fun. No tricks here, it's exactly what it looks like". As for getting it to appeal in the first place, I find that very few things can beat extremely cute cartoons.

    To that end, I present my first dabble in interactive educational whaddyamacallits: The Elementals, a fun periodic table where every element has its own unique personality.

    The Elementals

    It's available as an iPhone app initially but I'll be venturing into the Android Marketplace soon and putting it online as a web app.

    Available on the App Store

    Comments

  • Appington concept

    Written 11 Oct 2010

    Ideas,Design,iOS,Geek

    Note: this is a concept sketch only. This doesn't actually exist. It'd be cool if it did, though.

    Appington. Your applications brought to you.

    Appington LogoAppington is, fundamentally, a single-application VNC client with a simple interface for task switching. Where most VNC applications present the user with the entire screen, Appington only shows a single window at any one time. This simplified interface makes interaction easier and saves on client application memory and reduces data transfer allowing the viewer to be more responsive. In some applications, this data transfer saving may be used to facilitate audio capture and transmission.

    Applications list

    This screen shows a list of all available applications grouped by first letter. In the lower-left, the user can toggle between listing all applications or only listing currently running applications. The right-hand panel shows more information about the selected application. In this example, Google Chrome is selected and running. The current memory and CPU usage are shown along with a note of how many active windows the application has. Because Chrome is currently running, the option to quit is shown. If we had selected an unlaunched application, this button would show the option to launch. In case of emergencies, there is always the option to Force Quit a running application.

    Application window (portrait)

    This shows a standard single application window. The button in the top left would return the user to the previous screen. From the right, the remaining buttons allow the user to capture a screen shot, maximize the application window to match the current iPad viewport (if possible), refresh the current screen (in case of render errors) and access the application's menu. In OS X, menu access would be accomplished by way of the accessibility API. At the moment, I'm not sure how it would work on other OSs.

    Application window (landscape)

    This shows a single window of an application with multiple windows. You'll notice the extra button at the top between Menu and Refresh. This menu will allow you to select which window you want to access between however many the application currently has open.

    Other images

    The partner application to this is a modified VNC server running on the host machine. It is responsible for window management, task-switching, menu parsing and audio capture (à la SoundFlower). If there is already a VNC server running, the partner app will leave the standard VNC functionality alone and act purely as a helper, providing the extra functionality but not using extra memory by duplicating functionality. This is a variation of noVNC using the python proxy to manage the socket connection allowing the client to be built in PhoneGap using HTML 5.

    Like I said at the top, this hasn't been built yet. It'd be cool if someone did build it, though.

    Comments

  • ...and a salesman, too.

    Written 6 Oct 2010

    Not Geek,Opinion

    It seems to be a fundamental aspect of the world that, whatever you do for a living, you have to do that and be a salesman. When I say selling, I don't mean the purely business related contract-signing, accounting and banking aspect of sales, I mean really 'Selling yourself'. Marketing, if you will. The bit of the process that involves firm handshakes, giving presentations at conferences, reminding people at every opportunity that you are selling something they need. Even if they don't know they need it. Even if they don't need it.

    You could be the greatest web developer known to the history of the interweb creating progressively-enhanced, backwards-compatible masterpieces of semantic mark-up which not only push the boundaries in the latest Webkit nightlies but still fly pixel-perfect in IE6 and you still wouldn't be able to run your own agency without selling your services.

    Your iPhone app might be 'The Greatest App Ever Invented' combining the power of Google, the ease of use of Twitter and the graphics of Epic Citadel. It might prove the Riemann Hypothesis, remind you of birthdays, cure cancer all while showing pictures of cats falling asleep but unless somebody actually knows it exists, it's no more useful than those apps that play the noises of bodily functions while simultanesouly being less succesful. By putting it in the iTunes Store you are technically selling it but you're not 'selling it'.

    The same situation applies in every industry – writing books, making sandwiches, playing piano, juggling. Unless you are lucky enough to be 'discovered' by someone with the ability to sell but without anything to actually sell, there is no difference between you and everybody else in your field. Despite what you may have learnt in school, you do not get to the top of the class by being the smartest. You get to the top by putting your hand up when the teacher asks a question.

    A few months back I saw an article entitled 'Talent does not go unrewarded'. I've seen too many shy, socially awkward developers who won't progress past the minimum acceptable salary for their job title to believe this. More accurately, I'd say 'Talent does not go unrecognised'. They don't get rewarded for their technical wizardry, they get rewarded for convincing their bosses they're worth more than they're currently being paid. For selling themselves.

    Evan Williams' recent step down from CEO of Twitter to focus on product develpment strikes me as the developer's ideal – all the success and reward (financial and kudos) without the daily requirement to constantly sell. Of course, Twitter wouldn't have gotten to where it is if he hadn't been able to take on that role along the way.

    Comments

  • Bad tweet, go to your room

    Written 6 Oct 2010

    Opinion

    Disclaimer: I'm not a social media guru, consultant, expert, doctor, nurse, midwife, engineer, ninja or rockstar. Because of this, you know you can trust what I say about social media because I'm not trying to sell it to you. You can also ignore it because you're not paying me.

    I don't tend to say much on the subject of Twitter. I also don't tend to say that much on Twitter itself. That said, I do spend a lot of time on the Internet so here are some things you should stop doing now. Like, right now. Call it ‘Social media bad practice’ if you will, or ‘Tips and Tricks for a Tidier Tweet’ if that's the kind of thing you're into. Whatever, stop doing these:

    Autotweeting from 3rd party apps

    When you use one of the many apps that track your weight, book-reading habits, location, tweetability or shoe-size, disable the 'post to Twitter?' option, please. When I see these posts, all I read is either:

    “According to amIanEejit.com, I'm an Eejit. Are you? Try it now.”

    or

    “I need public validation. Did I do good? Did I?”

    Tweeting a shortened URL which performs an action on the logged-in user's account

    Of course, fault for this should also be spread equally between the tweeter, the website which allows GET operations to modify data and the user who isn't wary enough of shortened URLs to expand them first. The person who creates the shortened URL without being aware of the consequences is to blame and an eejit. If you're not sure what I'm talking about, here's an example:

    1. You have an account on website X which you are logged into
    2. I have an account on website X which I am logged into
    3. Website X allows you to delete your account by going to www.example.com/deletemyaccount.php
    4. You copy that URL and shorten it using bit.ly
    5. You tweet “Hey, this is what I think of Website X: http://bit.ly/madeupthing”
    6. I click the link
    7. My account on Website X gets deleted
    8. I stab you with pencils.

    Using inappropriate hashtags to piggy-back on an unrelated discussion

    Some people use #hashtags as tweet meta data providing an extra piece of context on the tweet – “Om nom nom #fridaymorningbaconroll” – while others use them to create fluid, transient chatrooms. Where in the past you'd have used IRC and created a relevant room, using Twitter and a hashtag, you can jump into a conversation and out again without even trying. If you attempt to barge your way in with irrelevant comments, advertising nonsense or general eejicy, you act no better than an out-and-out-spammer and I don't follow no spammers.

    Flooding followers with real-time reporting

    Use a separate account for this kind of thing. Macrumors do this whenever there's one of those big Steve Jobs parties – if you want to follow all the info, follow the @macrumorslive account. Similarly, Fridaymix do the same thing. Discussions happen with the @fridaymix account while the announcements of what is currently being played come from @fridaymixdj.

    Related: Retweeting your other account.

    If I wanted to follow the other account, I'd follow the other account.

    Also related: Retweeting your own main account

    I heard you. Don't be the guy at the party with one punchilne that you tell again and again. I already know that guy. I don't follow him on Twitter.

    Posting quotes from conference presentations without context or grammar

    This doubly applies if the quote sounds like a half-hearted Zen koan.

    “Listen to the youth. They have younger voices.”

    “Use torches to light the way. Technology is your torch.”

    This triply applies if you use antimetabole

    “Don't follow the herd, herd the followers.”

    “Don't live beyond means, have meaning beyond living.”

    Of course, the worst is probably tweeting about your own blog post in which you discuss tweeting as if it actually matters.

    Comments

  • Writing a Plex Plugin Part III

    Written 30 Sep 2010

    Geek,Development

    This is the final part of my walkthrough of the Plex Media Server Transmission plugin.

    Right.

    We've done the required built-in functionality (preference management, for instance) and the bits that talk to Transmission itself. Basically, we're done. Anything else added here is just extra. That is, of course, the best reason to add stuff here. As I have previously ranted at length, there's no point doing anything if you aren't trying to do it as well as it possibly can be done. In this particular instance, that manifests itself in the ability to browse, search for and download torrents all within the Plex client interface.

    EZTV

    I love EZTV. It makes things easy. Previous versions of this plugin included an EZTV search but after rooting around in the source of the µTorrent plugin, I found some nifty code which turned me onto the clever XML parsing Plex can do.

    Note: although some of this stuff looks clever, all the cleverness was done by the Plex dev team and the author of the µTorrent plugin. I'm a good copy-and-paster.

    This function grabs the H2s from the page http://ezrss.it/shows/. If you go there, you'll see that the page lists every TV show in EZTV. The original µTorrent function listed everything but there are a lot of shows there now so it was actually taking a long time just to get that list. As they've split the page up by section, we can just grab the bits we want. This is going to be a full page in Plex (not a popup) so we're using a MediaContainer.

    def TVShowListFolders(sender):
      dir = MediaContainer()

    Using the built-in XML module, we can simply pass in a URL and get back an object containing the hierarchical structure of the entire page. Seriously, how simple is this? As it's HTML, add in the option isHTML=True.

      showsPage = XML.ElementFromURL(
                    'http://ezrss.it/shows/', 
                    isHTML=True, 
                    errors='ignore'
                  )

    Now that we have the whole page structure, take the chunks of the page we want. All the sections we want (and one we don't) are divs with the class 'block' so use that in xpath to pull them out.

      blocks = showsPage.xpath('//div[@class="block"]')

    The first block is the one we don't want (if you look at the page, it's the one that lists all the letters) so we remove it.

      blocks.pop(0)

    For each of the remaining blocks, find the text in the first H2. That is the letter title of the section ('A', 'B', 'C', etc). Add that to Plex as a menu item then return the entire list.

      for block in blocks:
        letter = block.xpath("h2")[0].text
        dir.Append(
          Function(
            DirectoryItem(
              TVShowListSubfolders,
              letter,
              subtitle=None,
              summary=None,
              thumb=R(ICON),
              art=R(ART)
            ),
            letter=letter
          )
        )
      return dir

    I hope I'm not the only one impressed with that (although I have a feeling I might be). Using just a couple of lines from the XML module and a sprinkle of xpath and we've got another menu, dynamically generated from a third-party website. If EZTV ever change their layout, it should be a simple matter of changing the xpath to match and we're done. Again.

    We can now do the same again but this time, we only pull out a single section based on the letter passed in.

    def TVShowListSubfolders(sender, letter):
      dir = MediaContainer()
      showsPage = XML.ElementFromURL(
                    'http://ezrss.it/shows/',
                    isHTML=True,
                    errors='ignore'
                  )
      blocks = showsPage.xpath(
                '//div[@class="block" and h2 = "%s"]' % letter
               )

    Remembering to ignore any 'back to top' links, write out a list of the shows in this section. These will call the TVEpisodeList method next.

     for block in blocks:
      for href in block.xpath('.//a'):
       if href.text != "# Top":
        requestUrl = "http://ezrss.it" + href.get("href") + "&mode=rss"
        dir.Append(
         Function(
          DirectoryItem(
           TVEpisodeList,
           href.text,
           subtitle=None,
           summary=None,
           thumb=R(ICON),
           art=R(ART)
          ),
          name=href.text,
          url=requestUrl
         )
        )
     return dir

    This lists all available torrents for the chosen show. By this point, you should be familiar with how this works. We're using the XML module to grab the page at the URL (this time it's an RSS feed so we don't need to parse it as HTML); we use XPath to iterate through the items in the feed; we generate a menu item from the data which will call a function when selected; we append that to a MediaContainer then return the whole thing to Plex. Done. The AddTorrent function was defined higher up.

    def TVEpisodeList(sender, name, url):
     dir = MediaContainer()
     feed = XML.ElementFromURL(url, isHTML=False, errors='ignore').xpath("//item")
     for element in feed:
      title = element.xpath("title")[0].text
      link = element.xpath("link")[0].text
      dir.Append(
       Function(
        DirectoryItem(
         AddTorrent,
         title,
         subtitle=None,
         summary=None,
         thumb=R(ICON),
         art=R(ART)
        ),
        torrentUrl=link
       )
      )
     return dir

    Adult considerations...

    There is currently a section in the plugin which will allow you to search IsoHunt. This might get dropped in future versions of the plugin as results from IsoHunt are almost exclusively...ahem...adult, regardless of search terms. Sure, that might be exactly what you were looking for but if you were actually looking for Desperate Housewives, you might be surprised when your file comes down and it's actual 'desperate housewives'...

    Search EZTV

    The final part is a straightforward search of EZTV. The interesting thing to note is that this uses a different type of menu item. Where normally, you'd use a DirectoryItem in a Function, this uses an InputDirectoryItem in a Function. This type of menu item will pop open an on-screen keyboard before calling the target function giving you the opportunity to grab some user input.

    It's appended to the menu in the usual way:

     dir.Append(
      Function(
       InputDirectoryItem(
        SearchEZTV,
        L('MenuSearchTV'),
        "Search the TV shows directory",
        summary="This will use EZTV to search.",
        thumb=R(SEARCH),
        art=R(ART)
       )
      )
     )

    By the way, I think there's a minor bug in the InputDirectoryItem in that it doesn't like it when subtitle is passed as a named argument. I should probably file that as a bug with Elan.

    When the user has entered their input and submitted, the named Function SearchEZTV is called with the standard argument sender and the extra argument query containing the user's input.

    This function was a lot longer in the previous version of the Framework. It was so much simpler this time round.

    def SearchEZTV(sender, query=None):
      dir = MediaContainer()
      url = "http://ezrss.it/search/index.php?simple&mode=rss&show_name="
      if query != None:
       url += "%s" % query
      feed = XML.ElementFromURL(
              url, 
              isHTML=False, 
              errors='ignore'
             ).xpath("//item")
      if feed == None:
        return MessageContainer("Error", "Search failed")
      if len(feed) == 0:
        return MessageContainer("Error", "No results")
      for element in feed:
        title = element.xpath("title")[0].text
        category = element.xpath("category")[0].text
        link = element.find("enclosure").get("url")
        size = prettysize(int(element.find("enclosure").get("length")))
        dir.Append(
          Function(
           DirectoryItem(
            AddTorrent,
            title,
            subtitle=None,
            summary="Category: %s\nSize: %s" % (category,size),
            thumb=R(ICON),
            art=R(ART)
           ),
          torrentUrl=link
         )
        )
      return dir

    Done

    That's it. The only other little thing to mention is how handy it is to use the built-in Log function. The first argument is a standard Python string, the second is 'Should this only turn up in the console when in debug mode?' to which the answer will almost always be 'True'. There is a third argument but unless you're messing with character encodings, you don't need to worry about it.

    Log("Message to log is: %s %d" % (errorString, errorCode), True)

    Go, make...

    If you made it to the end here, you're probably either keen to start making your own Plex plugins or my wife who I am going to get to proofread this. Assuming you're the former, here are some handy links:

    • Online plugin development manual

      There are plenty of bits missing but it's still the best reference available for the framework.

    • Plex forums

      Particularly the Media Server Plugins forum

    • Plex Plugins Lighthouse

      This is where bugs are filed, suggestions made and final plugin submission happens. It's handy for picking little tips if someone else has had the same problem as you.

    If you want to use the plugin, it's available in 'Plex Online' in Plex/Nine or 'Plex App Store' in Plex/Eight. If you'd like to read through the complete source, you can download the zipped .Bundle.

    Transmission Plugin for Plex Media Server v1.0 [Zip - 1.1MB]

    Comments

  • Writing a Plex Plugin Part II

    Written 23 Sep 2010

    Geek,Development

    This is part two of my walkthrough of the Plex Media Server Transmission plugin.

    The previous part dealt with the basic required functions and preparing the main menu. This bit goes through the torrent control and the next will cover the built-in site-scraping functionality. To be honest, I'm not sure how much of this middle section will be of use to anyone but other torrent client plugin makers. The cool stuff really happens in the next one. Think of this as the difficult second album that you have to listen to before the return-to-form third.

    Listing the torrents

    This is the main interface to Transmission. Using the RTC method from before, this prepares a request to send via HTTP and reacts depending on the result (or error) we get back. First of all, we say we want information ('torrent-get') and we specify what info we want (the 'fields')

    def TorrentList(sender):
      error, result  = RTC("torrent-get",
        { "fields": [
          "hashString","name","status",
          "eta","errorString",
          "totalSize","leftUntilDone","sizeWhenDone",
          "peersGettingFromUs",  "peersSendingToUs",  "peersConnected",
          "rateDownload",      "rateUpload",
          "downloadedEver",    "uploadedEver"
        ] }
      )

    If we get an error back, we check what it was.

      if error != None:
        if error != "Connection refused":
          return MessageContainer(
              "Transmission unavailable",
              "There was an unknown error."
          )
        else:
          return MessageContainer(
              "Transmission unavailable",
              "Please make sure Transmission is installed and running."
          )

    Now we have our information, we create a MediaContainer to display it. We'll be building these entries up as if they were standard library MediaItems although the final action will not be to play them.

      elif result["torrents"] != None:
        dir = MediaContainer()

    For each set of torrent information we get back, we need to prepare the info and make it pretty.

        for torrent in result["torrents"]:
          progress    = 100;
          summary = ""
     
          if torrent["errorString"]:
            summary += "Error: %s\n" % (torrent["errorString"])

    If we have some time until we're done and we're not seeding, display the progress as "12.3 MB of 45.6 GB (0%)". We add this to the MediaItem's summary field. This is where we use the prettysize and prettyduration functions we imported at the top. They take a computer-friendly value (1048576 bytes) and return a human-friendly one (1MB).

      if torrent["leftUntilDone"] > 0 and
        torrent["status"] != TRANSMISSION_SEEDING:
        progress = ((torrent["sizeWhenDone"] - torrent["leftUntilDone"]) /
              (torrent["sizeWhenDone"] / 100))
     
        summary += "%s of %s (%d%%)\n" % (
            prettysize(torrent["sizeWhenDone"] - torrent["leftUntilDone"]),
            prettysize(torrent["sizeWhenDone"]), progress
          )

    Similarly, if there's an estimated time until the file is finished downloading, add that to the summary as "3 days remaining"

        if torrent["eta"] > 0 and torrent["status"] != TRANSMISSION_PAUSED:
          summary += prettyduration(torrent["eta"]) + " remaining\n"
        else:
          summary += "Remaining time unknown\n"

    Display download status ("Downloading from 3 of 6 peers") and download and upload rates ("Downloading at 3KB/s, Uploading at 1KB/s").

      if torrent["status"] == TRANSMISSION_DOWNLOADING:
        summary += "Downloading from %d of %d peers\n" % (
          torrent["peersSendingToUs"],
          torrent["peersConnected"])
        summary += "Downloading at %s/s\nUploading at %s/s\n" % (
          prettysize(torrent["rateDownload"]),
          prettysize(torrent["rateUpload"]))

    For all other downloading statuses, we don't need extended information so we just return a human-friendly version of the status we get back (we do this via another method below).

      else:
        summary += TorrentStatus(torrent)

    If we're seeding (the torrent has finished downloading and we're just uploading now), write out information about the uploading.

      else:
        if torrent["status"] == TRANSMISSION_SEEDING:
          summary += "Complete\n"
          progress=100
          if torrent["downloadedEver"] == 0:
            torrent["downloadedEver"] = 1

    "45.6GB, uploaded 22.8GB (Ratio 0.50)" and some detail about the people we're uploading to.

      summary += "%s, uploaded %s (Ratio %.2f)\n" % (
        prettysize(torrent["totalSize"]),
        prettysize(torrent["uploadedEver"]),
        float(torrent["uploadedEver"]) / float(torrent["downloadedEver"]))
      if torrent["status"] == TRANSMISSION_SEEDING:
        summary += "Seeding to %d of %d peers\n" % (
          torrent["peersGettingFromUs"],
          torrent["peersConnected"])
        summary += "Uploading at %s/s\n" % (
          prettysize(torrent["rateUpload"]))

    Icon generation

    The next addition was a bit of a tricky point for this version of the plugin. Previous versions generated the thumbnail icon dynamically using the Python Imaging Library (PIL). It would create a progress bar showing the exact percentage and write the name of the file on the icon. In order to be able to achieve this, PIL had to be imported which generated a whole bunch of deprecation warnings. There are rumours that a future version of the plugin framework will include some functionality to generate images on-the-fly (possibly a variation of PIL itself) but until then, I decided the best way forward would be to generate the images by hand and include them in the plugin. This meant that I could either generate 101 images (0% - 100%) or display the percentage rounded off. In order to save space, I went with rounding to the nearest 10%.

      nearest = int(round(progress/10)*10)

    The last thing to do in this loop (remember, we're still looping through the torrent information we received all the way back up at the top of the page) is to actually add this item. It is added as a PopupDirectoryItem so that selecting it will display a context menu of action choices specified in the TorrentInfo method below. With that, we also add the summary we've spent so long crafting, the percentage icon as the thumb and a couple of extra bits of information to help later functions know what to do.

      dir.Append(
        Function(
          PopupDirectoryItem(
            TorrentInfo,
            torrent["name"],
            summary=summary,
            thumb=R("%s.png" % nearest)
          ),
          name = torrent["name"],
          status = torrent["status"],
          hash = torrent["hashString"]
        )
      )

    To finish this menu, we add the same functions that are available to individual torrents but acting on all – 'Pause all' and 'Resume all' – then return the menu to Plex for display.

      dir.Append(
        Function(
          DirectoryItem(
            PauseTorrent,
            L('MenuPauseAll'),
            subtitle=None,
            summary=None,
            thumb=R(PAUSE),
            art=R(ART)
          ),
          hash='all'
        )
      )
      dir.Append(
        Function(
          DirectoryItem(
            ResumeTorrent,
            L('MenuResumeAll'),
            subtitle=None,
            summary=None,
            thumb=R(RESUME),
            art=R(ART)
          ),
          hash='all'
        )
      )
      return dir

    Here's the TorrentStatus lookup. Again, this uses the built-in localisation function 'L' to display the text and, again, I still haven't actually translated any of it so there's still only english. I must get round to that eventually.

    def TorrentStatus(torrent):
      if torrent == None or torrent["status"] == None:
        return L('TorrentStatusUnknown')
      elif torrent["status"] == TRANSMISSION_WAITING:
        return L('TorrentStatusWaiting')
      elif torrent["status"] == TRANSMISSION_CHECKING:
        return L('TorrentStatusVerifying')
      elif torrent["status"] == TRANSMISSION_PAUSED:
        return L('TorrentStatusPaused')
      elif torrent["status"] == TRANSMISSION_DOWNLOADING:
        return L('TorrentStatusDownloading')
      elif torrent["status"] == TRANSMISSION_SEEDING:
        return L('TorrentStatusSeeding')
      else:
        return L('TorrentStatusUnknown')

    Torrent action menu

    This is the popup menu displayed when you select one of the listed torrents. The only thing to notice from these is that the option to pause is only shown for active torrents and the option to resume is only shown for paused torrents. The hash mentioned here is the id of the torrent which will be needed later.

    def TorrentInfo(sender, name, status, hash):
      dir = MediaContainer()
      dir.Append(
        Function(
          DirectoryItem(
            ViewFiles,
            L('MenuViewFiles'),
            subtitle=None,
            summary=None,
            thumb=R(ICON),
            art=R(ART)
          ),
          hash=hash
        )
      )
      if status == TRANSMISSION_PAUSED:
        dir.Append(
          Function(
            DirectoryItem(
              ResumeTorrent,
              L('MenuResume'),
              subtitle=None,
              summary=None,
              thumb=R(ICON),
              art=R(ART)
            ),
            hash=hash
          )
        )
      else:
        dir.Append(
          Function(
            DirectoryItem(
              PauseTorrent,  
              L('MenuPause'),      
              subtitle=None,
              summary=None,
              thumb=R(ICON),
              art=R(ART)
            ),
            hash=hash
          )
        )
        dir.Append(
          Function(
            DirectoryItem(
              RemoveTorrent,
              L('MenuRemove'),
              subtitle=None,
              summary=None,
              thumb=R(ICON),
              art=R(ART)
            ),
            hash=hash
          )
        )
        dir.Append(
          Function(
            DirectoryItem(
              DeleteTorrent,
              L('MenuDelete'),
              subtitle=None,
              summary=None,
              thumb=R(ICON),
              art=R(ART)
            ),
            hash=hash
          )
        )
      return dir

    Torrent action functions

    Each of the torrent action functions called (ViewFiles, ResumeTorrent, etc.) could have been references to a more generic function with an action option passed in but I decided to keep them distinct and separate so that any extra customisation that might be done later would be easier to do rather than hacking it in. This isn't so much a problem with this plugin but if this were to be adapted for another torrent client, there might be specific hoops that needed jumped through.

    I won't go through each of them in detail as they are all very similar. Instead, I'll just describe one of them – RemoveTorrent.

    Each Function menu item (pretty much every menu item in this plugin) takes at least one argument: sender. This tells Plex where it's supposed to return control after it's finished here. The second argument is the id of the torrent we want to act on.

    def RemoveTorrent(sender, hash):

    We define the action to perform and the arguments to pass to Transmission.

      action = "torrent-remove"
      arguments = { "ids" : hash }

    Call Transmission's RPC via the RTC method defined earlier catching the results and any errors returned.

      error, result = RTC(action, arguments)

    If there's an error, any error, display it. Otherwise, display a success method. Both of these are displayed as a popup MessageContainer.

      if error != None:
        return MessageContainer("Error", error)
      else:
        return MessageContainer("Transmission", L('ActionTorrentRemoved'))

    Okay, so the middle section of the plugin might not be that interesting. Next time I'll cover the clever built-in XML parsing bits and everything'll be cool again. I promise.

    Comments

  • Writing a Plex Plugin Part I

    Written 16 Sep 2010

    Geek,Development

    In my awesome home cinema setup (about which I'll need to blog sometime), I use many pieces of inter-connected software and hardware. The hub, though, is the OS X computer in the middle running the Plex Media Server and the Transmission BitTorrent client. This post describes the plugin which ties them together.

    I've just released version 1.0 of the Transmission plugin for the Plex Media Server. As with all good software projects, there were actually many releases before 1.0 but I thought this was the right time to write up a walk-through of what the code looks like and does. I didn't write the initial version of the plugin but I've maintained it since v0.7 and now pretty much rewritten everything a couple of times. I'll post my walkthrough in a few installments because it's quite long.

    The code as it currently stands (v1.0) is available for download below but within a few days you should also be able to download it within 'Plex Online'

    Transmission Plugin for Plex Media Server v1.0

    As Michael A. pointed out in the comments, I haven't actually mentioned anywhere below what it is that the plugin does. For a quick overview, check out the Plex wiki Transmission page.

    This plugin is built to be compatible with Plex Plugin Framework v1 which initially looked like quite a major change from the previous version of the framework but isn't really that different. For Plex plugins, every time an action is performed, the system generates a URL and passes it down through its own menu structure until it gets to a plugin that handles that URL. The plugin then deals with the info in the URL however it likes. In the previous versions of this plugin, the URL was parsed manually and split into strings separated by '/'

    For example the URL:

    /video/transmission/id/status/action/

    would first cause Plex to look in its 'video' menu item and find the plugin that said it could handle 'transmission' URLs. The plugin would take the rest of the string and separate it out. First contact the Transmission application, ask for all information about the torrent called 'id', check its 'status' and then perform the 'action' if possible.

    The new plugin does pretty much the same except I no longer manually parse the URL. The plugin still registers with Plex to say it can handle URLs starting '/video/transmission' but then it passes functions through instead of URL-catching menu items. If you're familiar with JavaScript, it's like passing an anonymous function to handle something instead of catching the event manually.

    Anyway, here's the python code with a running commentary:

    Imports

    First we import the Plex Media Server framework:

    from PMS import *
    from PMS.Objects import *
    from PMS.Shortcuts import *

    These are a couple of handy functions from the very first version of the plugin which make the outputs much more readable.

    from texttime  import prettyduration
    from textbytes  import prettysize

    This next line actually causes Plex to issue a warning. These libraries won't all be available in the next version of the framework. Instead of urllib and urllib2, developers are to use the built-in HTTP module. Unfortunately, HTTP doesn't allow access to the headers of responses and the Transmission authentication system relies on exchanging a session ID via headers.

    import urllib,urllib2,base64

    Declarations

    Set up some constants to save typing later on

    PLUGIN_PREFIX = "/video/transmission"
    PLUGIN_TITLE = "Transmission"

    This is the first call to the Localisation module. Plex plugins allow for complete string localisation via a JSON file (I've only included English in this version because my torrent-related German and Japanese are poor). This will look in the JSON file for the key 'Title' and return whatever value is associated with it (or 'Title' if there is none).

    NAME = L('Title')

    More shorthand

    ART        = 'art-default.jpg'
    ICON      = 'icon-default.png'
    SETTINGS  = 'settings-hi.png'
    PAUSE      = 'pause-hi.png'
    RESUME    = 'resume-hi.png'
    SEARCH    = 'search-hi.png'
    TV        = 'tv-hi.png'
    
    TRANSMISSION_WAITING      = 1
    TRANSMISSION_CHECKING      = 2
    TRANSMISSION_DOWNLOADING  = 4
    TRANSMISSION_SEEDING      = 8
    TRANSMISSION_PAUSED        = 16

    Definitions

    Required

    Here is where we start the plugin code. This is one of the standard functions which gets called when the plugin is initialised.

    def Start():

    Tell Plex we can handle '/video/transmission' URLs and that our main function is called 'MainMenu'

      Plugin.AddPrefixHandler(
        PLUGIN_PREFIX, 
        MainMenu, 
        PLUGIN_TITLE, 
        ICON, 
        ART)
    
      MediaContainer.art = R(ART)
      MediaContainer.title1 = NAME
      DirectoryItem.thumb = R(ICON)

    Another standard function, this handles the preferences. To connect to Transmission, you need the URL and port it is running on (127.0.0.1:9091 if it's on the same machine as the Plex Media Server) and the username and password if you have set them.

    def CreatePrefs():
        Prefs.Add(id='hostname', type='text', default='127.0.0.1:9091', label='Hostname')
        Prefs.Add(id='username', type='text', default='', label='Username')
        Prefs.Add(id='password', type='text', default='', label='Password', option='hidden')

    This is called immediately after the preferences dialog is submitted. This is the most basic checking you can do but it could include a call to Transmission to verify the info provided.

    def ValidatePrefs():
        u = Prefs.Get('username')
        p = Prefs.Get('password')
        h = Prefs.Get('hostname')
        if( h ):
            return MessageContainer(
                "Success",
                "Info provided is ok"
            )
        else:
            return MessageContainer(
                "Error",
                "You need to provide url, username, and password"
            )

    You'll notice the return here is a MessageContainer. That's Plex's version of an alert. It doesn't generate a new page, just pops up a little window.

    Custom

    That was the end of the predefined functions, the plugin proper starts here. As Transmission requires a username, password and a short-lived session ID (since Transmission v1.53) to perform actions, we define a function which will attempt to make a connection with just username & password. Transmission will then send back a 409 Conflict response to basically say "Urk, that's not quite right. If you want to talk to me, you'll need this:" and give us our session ID in a header.

    def GetSession():
      h = Prefs.Get('hostname')
      u = Prefs.Get('username')
      p = Prefs.Get('password')
      url = "http://%s/transmission/rpc/" % h
      request = { "method" : "session-get" }
      headers = {}
      if( u and p and h):
        headers["Authorization"] = "Basic %s" % 
          (base64.encodestring("%s:%s" % (u, p))[:-1])
        try:
          body = urllib2.urlopen(
            urllib2.Request(
              url, 
              JSON.StringFromObject(request), 
              headers
            )
          ).read()
        except urllib2.HTTPError, e:
          if e.code == 401 or e.code == 403:
            return L('ErrorInvalidUsername'), {}
          return e.hdrs['X-Transmission-Session-Id']
        except:
          return L('ErrorNotRunning'), {}

    Once the HTTP module allows access to returned headers, we will be able to use something like this to set global authorisation once and forget about it:

    response = HTTP.Request(
        url, 
        { "method" : "session-get" }, 
        headers={}, 
        cacheTime=None
        )
    HTTP.SetPassword(h,u,p)
    HTTP.SetHeader(
      'X-Transmission-Session-Id', 
      response.headers['X-Transmission-Session-Id']
      )

    Remote Transmission Calls

    This uses the RPC API of Transmission to do everything we need. We pass into the function 'What we want to do' and 'Who we want it done to' basically.

    def RTC(method, arguments = {}, headers = {}):
      h = Prefs.Get('hostname')
      u = Prefs.Get('username')
      p = Prefs.Get('password')
      url = "http://%s/transmission/rpc/" % h
    
      session_id = GetSession()
    
      request = {
        "method":    method,
        "arguments":  arguments
      }

    Setup authentication here because, even though we've already gotten the session ID, it's useless if we don't actually use it.

      if( u and p ):
        headers["Authorization"] = "Basic %s" %
          (base64.encodestring("%s:%s" % (u, p))[:-1])
    
      headers["X-Transmission-Session-Id"] = session_id

    Now that we've built our instruction, throw it at Transmission and see what comes back.

      try:
        body = urllib2.urlopen(
          urllib2.Request(
            url, 
            JSON.StringFromObject(request), 
            headers)
          ).read()
      except urllib2.HTTPError, e:
        if e.code == 401 or e.code == 403:
          return L('ErrorInvalidUsername'), {}
        return "Error reading response from Transmission", {}
      except urllib2.URLError, e:
        return e.reason[1], {}
    
      result = JSON.ObjectFromString(body)

    We don't do error handling here as we want this function to be as generic as possible so we send anything we receive straight back to the calling function.

      if result["result"] == "success":
        result["result"] = None
    
      if result["arguments"] == None:
        result["arguments"] = {}
    
      return result["result"], result["arguments"]

    Menus

    Right, we've got our helper methods set up, we're ready to make our first menu. This is the main one we mentioned earlier.

    def MainMenu():

    You can set your menu screen to be laid out as “List”, “InfoList”, “MediaPreview”, “Showcase”, “CoverFlow”, “PanelStream” or “WallStream”. I'm keeping it simple here. Also, there's an extra call to GetSession just to check everything's fine and wake the app up.

        dir = MediaContainer(viewGroup="List")
        GetSession()

    Pretty much all the menu items throughout the rest of this plugin are added using the same code which boils down to:

      dir.Append(
        Function(
          DirectoryItem(
            FunctionName,
            "Pretty Menu Item Name",
            subtitle="Short subtitle",
            summary="Longer menu item summary and description",
            thumb=R(ICON),
            art=R(ART)
          )
        )
      )

    Starting in the middle, this reads as:

    • Create a DirectoryItem.
    • When this item is selected, use the function FunctionName to handle it.
    • Display the text "Pretty Menu Item Name" for this item
    • Display the text "Short subtitle" underneath this item (or None)
    • Display the text "Longer menu item summary and description" for this item if required (or None)
    • Use the resource called ICON (mentioned above) as the icon for this item
    • Use the resource ART as the background
    • This is a Function menu item
    • Finally, Append this to the current menu

    The first two main menu items are built exactly like that:

      dir.Append(
        Function(
          DirectoryItem(
            TorrentList,
            "Torrents",
            subtitle=None,
            summary="View torrent progress and control your downloads.",
            thumb=R(ICON),
            art=R(ART)
          )
        )
      )
      dir.Append(
        Function(
          DirectoryItem(
            SearchTorrents,
              "Search for a torrent",
              subtitle=None,
              summary="Browse the TV shows directory or search for files to download.",
              thumb=R(SEARCH),
              art=R(ART)
            )
          )
        )

    This is a special 'Preferences' item that will call the Prefs functions defined at the top.

      dir.Append(
        PrefsItem(
          title="Preferences",
          subtitle="Set Transmission access details",
          summary="Make sure Transmission is running and 'Remote access' is enabled then enter the access details here.",
          thumb=R(SETTINGS)
        )
      )

    A quick note: the function 'R' here, much like the localisation one 'L' above is a built-in helper function. It handles resources such as images. If you're developing a plugin and can't understand why your icon images are caching so much, it might be because you're going through this function.

    Send the directory (or Menu) back to Plex

        return dir

    The rest of the code deals with torrent control and some clever built-in site scraping functionality which I'll cover later.

    Comments

  • licences.xml

    Written 20 Aug 2010

    Ideas,Geek,Opinion

    JavaScript libraries and CSS frameworks are very popular these days. With each library, plugin, extension and template, comes another licencing statement. For most of these licences (MIT, for instance), you must include the licence statement in order to be able to use the code. In many cases, you also have to provide the original source and your own modifications. While, for uncompiled technologies such as these, this is a trivial matter, both this requirement and that of including the licence are awkward to implement if you like to minify your code. The licence is usually kept in an uncompressed comment at the top of the library (the YUI compressor specifically takes this into account with comments marked /*! */ ) and, although anyone can read your modifications to whatever you've used, post-minification code is much harder to follow (cf. three of my last four blog posts) and is really not 'in the spirit' of sharing your code.

    I'd like to be able to bundle all the licences and sources together outside the production files. Somewhere the interested user would be able to look them up if they liked but not somewhere that would automatically be downloaded on a standard visit. To that end, I have looked around for an established standard for this and not found anything. If you know of one, please let me know. Until I do find a good standard, here's my suggestion – a simple XML file located at /licences.xml in the format outlined below. It contains details of the file the licence pertains to, the uncompressed source (optional), the title of the licence and a URL where the full licence text can be found (on opensource.org or creativecommons.org, for instance). It also includes a (probably superfluous) shortname for the licence. I might remove that bit. You can optionally include this meta in your HTML if you want an explicit link between your source and the licence file:

    <meta name="licences" value="/licences.xml" />

    I'm currently undecided as to whether to go with XML or JSON. They're fairly interchangeable (barring XML attributes) but JSON takes less space. Then again, there's not as much need to save space in this file. Anyone have any recommendations? The entire format is, of course, up for discussion. Have I forgotten anything? Have I included anything unnecessary? I'm going to start using this in my projects until someone points out some major legal problem with it, I think.

    XML

     
    <licences>
     <licence>
      <source>
       <url>
        /includes/script/jquery/1.4/jquery.min.js
       </url>
       <uncompressed>
        /includes/script/jquery/1.4/jquery.js
       </uncompressed>
      </source>
      <deed>
       <title>
       MIT License
       </title>
       <url>
        http://www.opensource.org/licenses/mit-license.php
       </url>
       <shortform>
       MIT
       </shortform>
      </deed>
     </licence>
     <licence>
      <source>
       <url>
        /includes/script/custom/0.1/custom.js
       </url>
       <uncompressed>
        /includes/script/custom/0.1/custom.min.js
       </uncompressed>
      </source>
      <deed>
       <title>
       Attribution Share Alike
       </title>
       <url>
        http://creativecommons.org/licenses/by-sa/3.0
       </url>
       <shortform>
       cc by-sa
       </shortform>
      </deed>
     </licence>
    </licences>
    

    JSON

     
    {
     licences:{
      {
       source:{
        url:'/includes/script/jquery/1.4/jquery.min.js',
        uncompressed:'/includes/script/jquery/1.4/jquery.js'
       },
       deed:{
        title:'MIT License',
        url:'http://www.opensource.org/licenses/mit-license.php',
        shortform:'MIT'
       }
      },
      {
       source:{
        url:'/includes/script/custom/0.1/custom.min.js',
        uncompressed:'/includes/script/custom/0.1/custom.js'
       },
       deed:{
        title:'Attribution Share Alike',
        url:'http://creativecommons.org/licenses/by-sa/3.0',
        shortform:'cc by-sa'
       }
      }
     }
    }
    

    Comments

  • Maze 1k

    Written 19 Aug 2010

    Geek,Javascript,Development

    • Random perfect maze generation, solution and rendering in 1011 bytes

    Okay, this is the last one for a while. Really.

    Unlike my previous 1k JavaScript demos, I really had to struggle to get this one into the 1024 byte limit. I'd already done all the magnification and optimization techniques I knew of so I had to bring in some things which were new to me from qfox.nl and Ben Alman and a few other places. This, combined with some major code refactoring, brought it down from 1.5k to just under 1. In the process, all possible readability was lost so here's a quick run through what it does and why.

    First up, a bunch of declarations.

    These are the colours used to draw the maze. Note, I used the shortest possible way to write each colour (red instead of #F00, 99 instead of 100). The value stored in the maze array (mentioned later) refers not only to the type of block it is but also to the index of the colour in this array, saving some space.

    u = ["#eff", "red", "#122", "rgba(99,255,99,.1)", "#ff0"],

    This is used for the number of blocks wide and high the maze is, the number of pixels per block, the size of the canvas and the redraw interval. Thanks to Ben Alman for pointing out in his article how to best use a constant.

     c = 21,

    Here is the reference to the canvas. Mid-minification, I did have a bunch of function shortcuts here - r=Math.random, for instance - but I ended up refactoring them out of the code.

    m = document.body.children[0];

    For most of the time when working on this, the maze was wider than it was high because I thought that made a more interesting maze. When it came down to it, though, it was really a matter of bytesaving to drop the distinct values for width and height. After that, we grab the canvas context so we can actually draw stuff.

    m.width = m.height = c * c;
    h = m.getContext("2d");

    The drawing function

    The generation and solution algorithm is quite nice and all but without this to draw it on the screen, it's really just a mathematical curio. This takes a colour, x and y and draws a square.

    l = function (i, e, f) {
      h.fillStyle = i;
      h.fillRect(e * c, f * c, c, c)
    };

    Designing a perfect maze

    "You've got 1 minute to design a maze it takes 2 minutes to solve."
    - Cobb, Inception.

    Apologies for the unnecessary Inception quote. It's really not relevant.

    Algorithmically, this is a fairly standard perfect maze generator. It starts at one point and randomly picks a direction to walk in then it stops picks another random direction and repeats. If it can't move, it goes back to the last choice it made and picks a different direction, if there are no more directions, all blocks have been covered and we're done. In a perfect maze, there is a path (and only one path) between any two randomly chosen points so we can make the maze then denote the top left as the start and the bottom right as the end. This particular algorithm takes 2 steps in every direction instead of 1 so that we effectively carve out rooms and leave walls. You can take single steps but it's actually more of a hassle.

    For more on how this kind of maze-generation works, check out this article on Tile-based maze generation.

    Blank canvas

    This is a standard loop to create a blank maze full of walls with no corridors. The 2 represents the 'wall' block type and the colour #122. The only really odd thing about this is the code f-->0 which is not to be read 'as f tends to zero' but is instead 'decrement f by 1, is it greater than zero?'

    g = function () {
      v = [];  //our stack of moves taken so we can retrace our steps.
      for (i = [], e = c; e-- > 0;) {
        i[e] = [];
        for (f = c; f-- > 0;) i[e][f] = 2
      }

    By this point, we have a two-dimensional JavaScript array filled with 2s

    Putting things in

      f = e = 1;    // our starting point, top left.
      i[e][f] = 0; // us, the zippy yellow thing

    Carving out the walls

    This is our first proper abuse of the standard for-loop convention. You don't need to use the three-part structure for 'initialize variable; until variable reaches a different value; change value of variable'. It's 'do something before we start; keep going until this is false; do this after every repetition' so here we push our first move onto the stack then repeat the loop while there's still a move left on the stack.

      for (v.push([e, f]); v.length;) {

    P here is the list of potential moves from our current position. For every block, we have a look to see what neighbours are available then concatenate that cardinal direction onto the strong of potential moves. This was originally done with bitwise flags (the proper way) but it ended up longer. It's also a bit of a nasty hack to set p to be 0 instead of "" but, again, it's all about the bytes.

      p = 0;

    Can we walk this way?

    These are all basically the same and mean 'if we aren't at the edge of the board and we're looking at a wall, we can tunnel into it.'.

     if (e < 18 && i[e + 2][f] == 2) p += "S"
     if (e >= 2 && i[e - 2][f] == 2) p += "N";
     if (f >= 2 && i[e][f - 2] == 2) p += "W";
     if (i[e][f + 2] == 2) p += "E";
    
     if (p) { //    If we've found at least one move
      switch (p[~~ (Math.random() * p.length)]) { // randomly pick one

    If there was anything to note from that last chunk, it would be that the operator ~~ can be used to floor the current value. It will return the integer below thye current value.

    Take two steps

    This is a nice little hack. Because we're moving two spaces, we need to set the block we're on and the next one to be 0 (empty). This takes advantage of the right-associative unary operator 'decrement before' and the right associativity of assignment operators. It subtracts 1 from e (to place us on the square immediately above) then sets that to equal 0 then subtracts 1 from the new e (to put us on the next square up again) and sets that to equal the same as the previous operation, i.e. 0.

      case "N":
          i[--e][f] = i[--e][f] = 0;
          break;

    Do the same for s, w and e

        case "S":
          i[++e][f] = i[++e][f] = 0;
          break;
        case "W":
          i[e][--f] = i[e][--f] = 0;
          break;
        case "E":
          i[e][++f] = i[e][++f] = 0
        }

    Whichever move we chose, stick that onto the stack.

        v.push([e, f])

    If there were no possible moves, backtrack

      } else {
        b = v.pop(); //take the top move off the stack
        e = b[0]; // move there
        f = b[1]
      }
     }

    End at the end

    At the very end, set the bottom right block to be the goal then return the completed maze.

      i[19][19] = 1;
      return i
    };

    Solver

    This is the solving function. Initially, it used the same algorithm as the generation function, namely 'collect the possible moves, randomly choose one' but this took too much space. So instead it looks for spaces north, then south, then west, then east. It follows the first one it finds.

    s = function () {

    Set the block type of the previous block as 'visited' (rgba(99,255,99,.1) the alpha value makes the yellow fade to green).

      n[o][y] = 3;

    A bit of ternary background

    This next bit looks worse than it is. It's the ternary operator nested several times. The ternary operator is a shorthand way of writing:

    if ( statement A is true ) {
      Do Action 1
    } else {
      Do Action 2
    }

    In shorthand, this is written as:

    Statement A ? Action 1 : Action 2;

    In this, however, I've replace Action 2 with another ternary operator:

    Statement A ? Action 1 : ( Statement B ? Action 2 : Action 3 );

    And again, and again. Each time, it checks a direction, if it's empty, mark it as visited and push the move onto our stack.

    (n[o + 1][y] < 2) ?
      (n[++o][y] = 0, v.push([o, y])) :
        (n[o - 1][y] < 2) ?
          (n[--o][y] = 0, v.push([o, y])) :
            (n[o][y - 1] < 2) ?
              (n[o][--y] = 0, v.push([o, y])) :
                (n[o][y + 1] < 2) ?
                  (n[o][++y] = 0, v.push([o, y])) :

    If none of the neighbours are available, backtrack

                    (b = v.pop(), o = b[0], y = b[1]);

    Show where we are

    Finally, set our new current block to be yellow

      n[o][y] = 4;

    Are we there yet?

    If we are at the bottom right square, we've completed the maze

      if (o == 19 && y == 19) {
      n = g();    //Generate a new maze
      o = y = 1; //Move us back to the top right
      s()     //Solve again

    If we haven't completed the maze, call the solve function again to take the next step but delay it for 21 milliseconds so that it looks pretty and doesn't zip around the maze too fast.

      } else setTimeout(s, c);

    Paint it black. And green. And yellow.

    This is the code to render the maze. It starts at the top and works through the whole maze array calling the painting function with each block type (a.k.a. colour) and position.

        for (d in n) for (a in n[d]) l(u[n[d][a]], a, d)
      };

    Start

    This is the initial call to solve the maze. The function s doesn't take any arguments but by passing these in, they get called before s and save a byte that would have been used if they had been on a line themselves.

    s(n = g(), o = y = 1)

    Done

    This little demo isn't as visually appealing as the Art Maker 1k or as interactive as the Spinny Circles 1k but it is quite nice mathematically. There are now some astounding pieces of work in the JS1K demo competition, though. I do recommend spending a good hour playing about with them all. Don't, however, open a bunch of them in the background at the same time. Small code isn't necessarily memory-efficient and you could quite easily grind your computer to a standstill.

    Comments

  • Art Maker 1K

    Written 12 Aug 2010

    Geek,Javascript,Development,Design

    Even though the rules for js1k only let me make one submission, I couldn't stop myself making another. This one is inspired by those pseudo-romantic pictures that you get all over tumblr that get reblogged endlessly (actually, it was inspired by the blog That Isn't Art by someone with the same opinion as myself).

    It randomly creates a sentence, adds some softly-moving almost bokeh coloured-circles and look, I made an art! Wait for the sentence to change or click (or touch) to change it yourself.

    Art Maker 1k

    And of course, don't forget the original spinny-circles 1k

    Comments

  • The quest for Extreme JavaScript Minification

    Written 8 Aug 2010

    Development,Javascript,Geek,Guides

    As described in detail previously, I've recently taken part in the JS1K competition where you have to squeeze something cool and clever into 1024 bytes of JavaScript. The quest to condense has become quite addictive and I found myself obsessing over every byte. This is the kind of stuff that the Closure Compiler does quite well automatically but there are some cases where you just need to get in there and manually tweak.

    Here are some of the tricks I've picked up in my struggle for extreme minification:

    Basic improvements

    Use short variable names.

    This one's fairly obvious. A more useful addition to this is:

    Shorten long variable names.

    If you're going to be accessing an element more than once, especially if it's a built-in element like 'document', you'll save a few bytes every time you reference it if you create a shortened reference to it.

      document.body.style.overflow="hidden"
      document.body.style.background="red"
      (74 characters)
    

    can shorten to

      d=document;b=d.body;s=b.style;
      s.overflow="hidden";
      s.background="red"
      (69 characters)
    

    and any references to s after are going to save 19 characters every time.

    Remove whitespace

    This one's so obvious, I don't need to mention it.

    Set Interval

    The extremely handy setInterval function can take either a function or a string. If you give it an anonymous function declaration:

      setInterval(function(){x++;y--},10);
    

    You will use up more characters than if you give it just the inside of the function as a string:

      setInterval('x++;y--',10);
    

    But the outcome will be the same.

    Little-used aspects

    Not many people use JavScript's scientific notation unless they're doing scientific stuff but it can be a great byte saver. The number 100 is equivalent to 1 * 10^2 which is represented in JavaScript as 1E2. That's not a great saving for 100 but 1000 is 1E3, 10000 is 1E4. Every time you go up a factor of 10, you save 1 byte.

    Fight your good style tendencies

    In the war against space, you have to bite the bullet and accept that you may need to sacrifice some of your hard-earned practices. But only this once. Don't get in to the habit, okay?

    No zeroes

      0.5  = .5
    

    Yeah, it looks ugly but it works and saves a byte.

    Naked clauses

      if {
        :
        :
      } else y
    

    The y looks so naked out there. No braces to keep it warm. But if you only have one statement in your else clause, you don't need them...

    No semi-final. . . final-semi. . . Semi-colon. No final colon.

    You don't need a semi-colon on your last line, even if it does make it look as though you've stunted its growth.

    The final few bytes

    Operator precedence

    You don't need brackets. Brackets are handy for you as the programmer to remember what's going on when and to reduce ambiguity but if you plan correctly, most of the time you won't need brackets to get your arithmetic to work out.

      b.getMilliseconds()/(a*250) 
          is the same as
      b.getMilliseconds()/a/250 
    

    Shorthand notation

      l=l+1;l=l%14;
      l++;l%=14;
      l=++l%14;
    

    The three lines above are equivalent and in order of bytes saved.

    Shorthand CSS

    If you need to set some CSS values in your script, remember to pick the most appropriate short form. Instead of s.background='black', use s.background='#000' but instead of s.background='#F00', use s.background='red'. In the same vein, the statements margin="0px" and margin=0 mean the same but the latter saves bytes.

    Don't be generic

    One final thing to mention is that these little challenges are not the norm. If you find yourself trying to squeeze code down like this you're probably working on a very specific project. Use that to your advantage and see if there are any savings to be made by discarding your usual policies on code reuse. In the JS1K challenge, we're provided with a specific HTML page and an empty script tag. One good saving made here (and mentioned in my previous post) was the way I grabbed the reference to the canvas element. The standard method is to use the id assigned to the canvas.

      d.getElementById('c')
    

    Which is a good generic solution. No matter what else was on the page, no matter what order stuff was in, this would return the canvas. However, we have a very specific case here and the canvas is always going to be in the same place - the first child of the body element. That means we can do this instead:

      b.children[0]
    

    This makes use of the reference we grabbed to the body earlier. If the page were rearranged, this would stop working but as it won't, we've saved 8 bytes.

    In conclusion

    Yes, this is all quite silly but it's also fun and tricky. Attempting these kinds of challenges keep us developers mindful of what it is we actually do and that makes it an extremely productive silly hobby.

    Comments

  • Elementally, my dear JavaScript

    Written 4 Aug 2010

    Geek,Javascript,Development

    The Angry Robot Zombie Factory launched its second iPhone/iPad app this week. I haven't mentioned it much yet because I spotted a minor typo in the final version after it had been approved so I submitted an update immediately. To get an early copy (like those misprinted stamps where the plane is upside down), go check out The Elementals. It's free, too. It's a simple, cartoonish periodic table.

    Yesterday, the 1k JavaScript demo contest (#js1k) caught my eye. The idea is to create something cool using 1024bytes of JavaScript or less. I rootled around in the middle of The Elementals, grabbed the drawing function and 20 minutes later had made my entry.

    The code I submitted is quite minified but isn't obfuscated. When it's unfolded, you can follow the flow fairly easily.

    var d = document,
    b = d.body,
    s = b.style,
    w = innerWidth,
    h = innerHeight,
    v = b.children[0],
    p = 2 * Math.PI,
    Z = 3,
    x = tx = w / 2,
    y = ty = h / 2;
    

    The above is a bunch of declarations. Using things like d = document and b = d.body allows reuse later on without having to resort to the full document.body.style and saves a bunch of characters. When you've got such a small amount of space to play with, every character counts (mind you, the ZX81 only had 1k of RAM and look what you could do with that). Now that I'm looking at it, I think I could have tidied this a bit more. Darn. The sneaky bit about this code is the way we grab the reference to the canvas. The code d.getElementById('c') uses 21 characters but if we look at the provided HTML, we can use the fact that the canvas is the first child of the body element. The code b.children[0] uses 13 characters instead.

    s.margin = "0px";
    s.background = "black";
    s.overflow = "hidden";
    v.width = w;
    v.height = h;
    t = v.getContext("2d");
    

    This sets the provided canvas to be the full width and height of the window then grabs the drawing context of it so we can make pretty pictures.

    zi = function () {
     Z++;
     Z %= 14
    };
    m = function (X) {
     return (X * 200) % 255
    };
    

    Functions to be reused later. zi increases the number of spinning circles and is used by onmousedown and ontouchstart (oh yes, it works on the iPad, too). m is a mapping of the index of the circle to a colour. The 200 is arbitrary. I played about a bit until I found some colour combinations I liked.

     d.ontouchstart = function (e) {
     zi();
     tx = e.touches[0].pageX;
     ty = e.touches[0].pageY
    };
    d.onmousemove = function (e) {
     tx = e.clientX;
     ty = e.clientY
    };
    d.onmousedown = zi;
    

    Setting the event handlers.

    function r() {
     t.globalCompositeOperation = 'lighter';
    

    I played about with the various composite operations. Lighter seemed the nicest.

     t.clearRect(0, 0, w, h);
     t.save();
     x = x + (tx - x) / 20;
     y = y + (ty - y) / 20;
     t.translate(x, y);
    

    Originally, the circles followed the mouse pointer exactly but it lacked any life. By adding in this bit where the movement is delayed as if pulling against friction, it suddenly became a lot more fun and dynamic.

     var c = new Date();
     for (var i = 1; i <= Z; i++) {
      t.fillStyle = 'rgba(' + m(i) * (i % 3) + ', ' + m(i) * ((i + 1) % 3) + ',' + m(i) * ((i + 2) % 3) + ', 0.5)';
      t.beginPath();
      t.rotate((c.getSeconds() + i) / (i / 4) + (c.getMilliseconds()) / ((i / 4) * 1000));
      t.translate(i, 0);
      t.arc(-10 - (Z / 5), -10 - +(Z / 5), 100 - (Z * 3), 0, p, false);
      t.fill()
     }
    

    Erm. Yeah. In essence, all this does is figure out where to draw the circles, how big and what colour. It looks worse than it is. Line-by-line, it translates to:

    1. Find out the current time
    2. For each circle we want to draw,
    3. Pick a colour based on the index of the circle
    4. Start drawing
    5. Turn by some amount based on the time and the index
    6. Move by a small amount based on the index
    7. Actually draw, making the circles smaller if there are more of them.
    8. Fill in the circle with the colour.
    9. Right curly bracket.
     t.save();
     t.fillStyle = "white";
     for (var i = 1; i <= Z; i++) {
      t.beginPath();
      t.rotate(2);
      t.translate(0, 28.5);
      t.arc(-120, -120, 5, 0, p, false);
      t.fill()
     }
     t.restore();
     t.restore()
    }
    

    This does pretty much the same as the one above but always the same size and always the same colour. The t.save and t.restore operations throughout mean we can add the transformations onto each other and move stuff relative to other stuff without messing up everything. Goshdarn, that was technical.

    setInterval(r, 10);
    

    Kick it all off.

    That make sense? Good. Now go make your own js1k entry and submit it. Then download The Elementals. Or Harmonious.

    Comments

  • PhoneGap - The Drupal of App development

    Written 17 Jun 2010

    Geek,iOS,Development

    I'm a fan of Drupal even though I don't use it that often. I like that I can see exactly what's going on. I can easily follow the execution from URL request to page serve.

    What I usually end up doing on any Drupal project is:

    1. build the majority of the site in a few hours
    2. find one small piece of functionality missing that's absolutely essential
    3. dig into the core to make it happen
    4. find a simpler way of doing it and step out of the core a bit
    5. find an even simpler way and step back a bit more
    6. figure out how to do it in a single module file and put the core back the way it was.

    That probably seems utterly inefficient but it has served me well since Drupal 4 and it means I've got a really good picture in my head of the internal workflow.

    This is in stark comparison to other systems, particularly some .NET CMSs where a request comes in, something happens and the page is served. There are even some PHP frameworks and CMSs where everything is so abstracted, the only way you can get an accurate picture of what is happening is to already have an accurate picture of what is happening.

    I've used several different ones and I keep coming back to Drupal (also, recently, Perch, but that's besides the point here).

    “What on earth does this have to do with PhoneGap?” I hear you ask. Quite rightly, too.

    When I was planning Harmonious, I looked at various frameworks for turning a combination of HTML, CSS & JavaScript into an app - PhoneGap, Appcelerator Titanium, Rhomobile. Rhomobile (or the Rhodes Framework) is built on Ruby so I didn't investigate too far. That's not to say it's not a good framework, I couldn't say either way. The idea behind using one of these frameworks is to save you the time of having to learn Objective-C and seeing as I've only done very minimal amounts of Ruby, I'd be replacing 'learn Objective-C' with 'learn Ruby'. That said, I've always thought Ruby developers opinion of themselves was slightly too high.

    The first framework I properly spent some time with was Appcelerator. It seemed quite shiny and I liked having single interface access to compilation for iPhone and Android but I wasn't so keen on having to sign up for an account with them for no obvious reason. Some further investigation suggested that this was so you could send your project to their servers for proprietary cross-platform compilation of your desktop app. This is less useful, however, if you're developing just for iPhone and Android as for both, you need the SDK installed locally and the compilation is done on your own machine.

    The main thing that I wasn't comfortable with in Appcelerator was that there seemed to be a lot happening behind the scenes. This is not necessarily a bad thing, of course, but it started that little buzz in the back of my head that I get when working on .NET. When I press 'compile', I want to know exactly what it's doing. I want to know exactly how it takes my JavaScript and embeds it, when does it include its own API files and what do I change to make it do stuff it doesn't do by default?

    After that, I moved to PhoneGap (version 0.8.3) and found myself immediately falling into my Drupal workflow. The app fell into place in less than an hour (with a liberal sprinkling of jQTouch and the Glyphish icons). I then needed to take a screenshot and couldn't see an obvious way to do it but, due to the nature of PhoneGap being completely open-source, it was easy to spot where to jump into the code. I hacked in a screenshot function in another hour, spent another half hour making it better and another making it simpler. Just to complete the cycle, I have now wrapped up all my code into a plugin and removed my hacking from the core. Hmmm... that all seemed eerily familiar.

    That's not to say PhoneGap is perfect. All the benefits of a completely open-source project referred to previously also come with all the drawbacks. The current version (0.9.0) is fiendishly difficult to download and get started with. It has been split into one parent project and several child projects (one per mobile platform) and it's no longer obvious what you do. It's easy enough if you're already set up but actually getting there is tricky. The most common failing of any open-source project is also true: poor documentation. There's a wiki but it's mostly out-of-date. There's a section on phonegap.com called 'docs' but they're also out-of-date. There's an API reference but it's autogenerated from comments and is also out-of-date. The only place to get accurate information is the Google group but that's not documentation, that's solutions to problems.

    There have also been some claims that PhoneGap is unstable and crashes but personally, I haven't seen that. It's possible that the crashes and performance issues are the result of memory leaks in the JavaScript. Appcelerator automatically runs JSLint on your code before compilation so it will highlight any problems. If you can fit that into your standard workflow, you might be able to avoid some of the instability.

    Additional comments

    It seems that Disqus (the commenting system I'm using below) has some problems with Safari 5 & Chrome so this comment was sent via gist (I knew I shouldn't have stopped using Noodle).

    I'll respond later. I've just got back from the Apple store and have toys to play with.

    Comment from Jeff Haynie (@jhaynie)

    A few comments about Appcelerator.

    1. We're completely open source and you can see all our active development every single commit on github.com/appcelerator. We have plenty of outside core open source contributors.

    2. Yeah, to do what we're doing, it's complicated - much more than Phonegap - so it does mean with complexity it's hard to grok. however, the source is all there. Also, it's full extensible through our SDKs and we this SDK as the way we build Titanium itself.

    3. For Desktop, we _only_ upload to our build servers as a convenience to cross-platform packaging. Nothing mysterious and all the scripts we run are included (and open source) so you can run them on your own. Plenty of our customers do this behind the firewall. When you're developing locally (say on a OSX machine), it's all local during dev. Only cross-platform packaging is done as a convenience to developers. We have to pay for this bandwidth and storage and we do it to make it easier. And it's free.

    Hope this clarifies some of the above. Phonegap's a great project and we love the team - but I think we're trying to do different things and come at it from different approaches. In the end, this is good for developers as it gives everyone more choice based on their needs.

    Comments

  • List of Touch UI gestures

    Written 10 Apr 2010

    Geek,iOS,Design

    Just now, I'm trying to improve the UI for the Factory's first iPhone app. While doing this, I've come up with a list of available areas and gestures in a touch-driven app that you can use for actions. I thought I'd put them here so other people could point out where I've gone wrong and what I've forgotten:

    • Menus
      • Permanent on-screen menu
      • Transient on-screen menu (requires trigger)
      • Different screen menu (any number of them, requires trigger)
    • Static (can be overloaded with function based on position)
      • Single-tap
        • Single-tap 1 touch
        • Single-tap 2 touches
        • Single-tap 3 touches
      • Double-tap
        • Double-tap 1 touch
        • Double-tap 2 touches
        • Double-tap 3 touches
      • Touch and Hold
        • Touch and Hold 1 touch
        • Touch and Hold 2 touches
        • Touch and Hold 3 touches
    • Dynamic (Gestures)
      • Touch and Move 1 touch
        • Up
        • Down
        • Left
        • Right
      • Touch and Move 2 touches
        • Up
        • Down
        • Left
        • Right
        • Apart (Zoom)
        • Together (Pinch)
        • Rotate clockwise
        • Rotate anticlockwise
      • Touch and Move 3 touches
        • Up (Swipe)
        • Down (Swipe)
        • Left (Swipe)
        • Right (Swipe)
        • Apart (Spread)
        • Together (Gather)
        • Rotate clockwise
        • Rotate anticlockwise

    Comments

  • Scene and Herd archive

    Written 10 Mar 2010

    Cartoons,Not Geek

    Continuing the webcomic theme from yesterday, I finally uploaded the archive of strips from the webcomic I used to do in 2003.

    It actually started off as a cartoon on flyers advertising Baby Tiger gigs before developing into music reviews for a while before ending up in the final version.

    Start at the far end of the cartoon department, third floor.

    Comments

  • Rules: The Comic

    Written 9 Mar 2010

    Cartoons,Not Geek

    I found some old sketches at the weekend and decided that I shouldn't just leave them in a drawer doing nothing.

    I, therefore, present to you:

    The Rules

    It's kind of a web comic but it only has 19 issues, no plot and won't be continuing.

    Comments

  • The Shadow Government and a Hyperbagel

    Written 8 Mar 2010

    Not Geek,Design

    I listen to a bunch of podcasts. I watch the Daily Show and the Colbert Report. I listen to a lot of They Might Be Giants. When you combine this with the audiobooks I listen to, the shows I go to and the paper books I read, you start to spot a pattern. A slightly sinister pattern...

    • The Illiternati

    This originally started as a connectivity diagram of American Literary Non-fictionists but after I'd finished I realised it's not entirely American, it's not entirely non-fictionists. It's not entirely comedy and not entirely literary. After showing it to a friend though, he immediately suggested 'The New Illuminati' or possibly the Literary Illuminati. Maybe just the Illiternati. Any way round you have it, John Hodgman appears to be as some kind of Literpope in the middle of a literspiracy.

    From what I can figure, I need to write some world economics exposé with Planet Money, discuss the software I used to analyse the markets with This Week in Tech and appear onstage at The Moth to tell the audience how the experience changed my life then I can join the dots on the diagram and reveal the secret Iliternati symbol. I think it'll be somewhere between the CND logo and a hyperbagel.

    Comments

  • Some kind of monster

    Written 4 Mar 2010

    Cartoons,Not Geek

    Some kind of monster

    I've been trying to make myself sketch a lot more recently. This was mostly prompted by my decision to start up The Angry Robot Zombie Factory as an actual company doing web development and illustration.

    I've been keeping an almost daily sketch blog over on tumblr and promoting any good pieces over onto my actual illustration portfolio. At some point, I'll bring all these different sites and things together. Until then, here's a sketch of a few things from the last couple of weeks.

    Comments

  • Synchronised Podcasts

    Written 26 Feb 2010

    Ideas,Not Geek

    This must exist somewhere. I just can't find it.

    I listen to a lot of podcasts in a week and I use quite a few different computers. One desktop at home, one laptop while out and about and a PC and an iMac at work. I want some service (or combination of web service and application) that I can use to manage my podcast subscriptions regarless of where I am.

    At the moment, I have iTunes installed on my desktop, my laptop and the iMac at work and I have subscribed to my collection of podcasts in each of them. I want to be able to plug in my iPod and have it delete the podcasts I've listened to and get the latest episodes of each of my subscriptions. At the moment, I plug it into the desktop, copy on the latest 'Planet Money' and listen. A couple of days later, there's another episode released so I plug into my laptop and it offers the episode I've just finished listening to and the new one. A few days later, I'm working on the office iMac and plug in my iPod, it suggests the last weeks-worth of episodes. I have to manually go into every subscription and drag over the individual files that I want to listen to.

    This is, of course, ignoring my usual niggle about iTunes which is its insistence on pausing downloads with the message "iTunes has stopped updating this podcast because you have not listened to any episodes recently". No, keep downloading, iTunes. I didn't tell you to stop.

    What I'd like to have is a web site where I can put in my podcast subscriptions and it will track the latest episodes of each. I can then either point iTunes to this site so that I can point all my installations at it or it will provide an application which can be used to put the latest episodes onto my iPod. When I plug in my iPod, the application tells the site which ones I've listened to and it removes them from my listening queue. The application could, also be stored on the iPod itself to enable it to be used wherever the iPod is plugged in, not just on computers with iTunes.

    Am I explaining myself clearly enough? It just seems so simple, it should already exists within iTunes. It is entirely possible that Apple's recent acquisition of Lala could be the first step in an online iTunes which would solve these problems. If anyone has any suggestions for the best way to achieve this, please let me know. I thought of a way of doing it with Dropbox but it would only work if the music bit of my iTunes library weren't bigger than my Dropbox account.

    Comments

  • Still Life Under Ice

    Written 26 Jan 2010

    Photos

    I like the textures in this one but I might prefer it black & white. Hmmm.. undecided.


    Originally uploaded by thingsinjars on flickr

    Comments

  • Momiji?

    Written 26 Jan 2010

    Photos

    Originally uploaded by thingsinjars on flickr

    Comments

  • Appreciate the artisans

    Written 25 Jan 2010

    Opinion

    I know that every professional thinks their bit of the process is more important than people give them credit for. Designer's don't just colour in wireframes handed to them by the Information Architect. IAs don't just draw boxes and arrows. Copy writers don't just copy-and-paste the company brochure over the lorem ipsum.

    Now that I've said that, I must now point out: Developers don't get nearly enough credit.

    This may be something to do with the odd confusion that is 'web designer vs. web developer'. In some - and possibly the majority of - agencies, the web designer not only designs what the page looks like in Photoshop/Fireworks/Whatever but also produces the HTML templates, CSS and whatever JavaScript they feel comfortable with (the tutorials at jQuery for Designers probably help, too). In these agencies, if there is such a person as a web developer, they are most likely responsible for moving the relevant bits of HTML into template files, adding in any back-end integration and possibly writing some of the trickier JavaScript. The confusion arises in the other kind of agencies. The kind where web designers make Photoshop files and web developers turn them into HTML. The designer doesn't necessarily need to know anything about HTML, semantics or scripting. Not to minimise the importance of this kind of designer - they'll know a lot about typography, and visual relations, probably quite a lot about user experience and the process involved in bridging the gap between what the client wants to say and how the user wants to hear - but it's this kind of web developer I think doesn't get enough credit.

    If you're designing a site with a full knowledge of how it could be marked up, you will naturally - even if it's subconsciously - be marking it up in your head. This will influence your design and not necessarily in a bad way. You might ensure the semantics are just that little bit clearer or you might nudge these bits over that way so they can be grouped with those other ones there. If, however, you design with no thought at all about how this is going to be made, you will, most likely, do some things that you wouldn't otherwise. If your front-end developer can take this and turn it into a perfectly semantic, clean-coded masterpiece of HTML and CSS then apply JavaScript to progressively enhance the heck out of it and still keep it looking like you designed, they deserve to be lauded, applauded, praised and thanked. Publicly. The usual outcome of this situation is that the designer gets asked along to the awards ceremonies, puts it on their portfolio, an article in the Drum, happy. The developer gets a pat on the back from the team leader and asked if they could just tidy up how it looks in IE5.5 before they head home for the night, that'd be great, thanks.

    Sure, maybe we just need some better awards ceremonies for geeks. The kind of thing that the agency sales team will be able to brag about to potential customers (as that, in essence, seems to be the point of awards ceremonies) but I also think there might need to be a bit of a change of opinion in the industry. Just as designers don't just colour in wireframes, developers don't just open the designs in Photoshop and press 'Save for web...'.

    I hope this doesn't sound too ranty. These thoughts were prompted after seeing a few designer and copy writer portfolios which contained sites that either I'd built or one of my team had built. Writers credited, designers credited, developers (who built some awesome stuff on them, by the way) lost in the mists of time.

    Comments

  • Heidi

    Written 22 Jan 2010

    Not Geek

    Download the file

    This probably won't mean much to anyone unless you're familiar with the Japanese Heidi cartoon which was popular in Germany in the 80s.

    When I first heard the theme, I thought the intro should have gone like this.

    Comments

  • User style

    Written 15 Jan 2010

    Opinion

    A few years ago, I made a prediction about the way the web was going and so far it hasn't come true but it's definitely coming closer. To me it seems that the logical extension of us developers separating style and substance – what we've been doing for years with semantic mark-up – is for the general consumer to take that substance and give it their own style. I'm in no way suggesting that everyone become a designer. That would be a terrible, terrible thing. What I mean is that the consumer takes in/reads/experiences whatever it is you're giving to them in the manner that best suits them. There are many examples of what I mean around already but they're still not quite where I think they will end up.

    RSS

    We (web developers) already provide RSS feeds on our sites. By subscribing to a site's RSS feed, you get the content delivered directly to your RSS reader. As long as the site is providing the full article content (shame on you, if not) the consumer gets to see your content in a design format you have little control over. There is a basic level allowed for RSS formatting but nothing you can rely on. The control for the visual appearance of your content is now in the hands of the designer of the reader and the consumer (by way of choosing which reader they use).

    userstyle.css

    This was what initially prompted my thoughts on the subject. I've used Opera as my main browser for almost 10 years and I've always liked the Author mode/User mode switch. In essence, you can quickly toggle between seeing a web page as it was intended by the designer or disregarding the original layout and applying your own stylesheets to it. For the most part, this is used to be able to set high contrast for visually impaired users or to test various criteria (showing only images that have missing alt attributes, for example) but they can be used to produce any visual effect achievable with CSS.

    User stylesheets can also be assigned on a per-site basis rather than globally which means that you could have your Google results rendered in courier, right-aligned in green on black while your facebook pages can be set in Times in a sepia colourscheme.

    As with many things on the web, userstyles became a lot more popular once this functionality was available in Firefox (via the add-on Stylish) and not just Opera. Now there's a growing community of Userstyle developers and a directory of styles. Unfortunately, this is still not quite ready for mainstream use. It requires at least a basic level of technical ability to enable userstyles and to install them.

    userscript.js

    The userstyles community is, however, dwarfed in comparison to the userscript community. In pretty much exactly the same way that userstyles work, users can execute a specific Javascript file whenever they visit a site. Again, this can be enabled in Opera using site preferences and in Firefox using the Greasemonkey add-on. These scripts can completely change the way a site functions as well as how it looks. Combine them with userstyles (which userscripts can include automatically) and the only thing you can rely on remaining from your original design is the URL. There's a massive database of userscripts available.

    Again, though, these are still just that little bit too hard. The standard user isn't going to install the extension, isn't going to browse for scripts and isn't going to run Opera so these are still a bit too far away.

    Grab now, read later

    There are now quite a few sites where you can save stuff to read later. If you find an interesting article or a funny blog post but don't have time to read it or if it appears on a site with a garish and unusable design, you can send it to Instapaper or Evernote . You can then read it in their interface, on your iPhone, on your Kindle... all separated from your design.

    It's not only text that gets this treatment, you can use Ember and LittleSnapper to grab and store visuals for later perusal or use Huffduffer to collect any audio files you find and serve them back to you as your very own personalised podcast. Again, this is your content separated entirely from the way you wanted it seen. And that's a good thing.

    For content creators, all this means is that your content can be consumed anywhere, even via sites, tools and delivery mechnisms you've never heard of. Designers, don't despair, users aren't suddenly going to take their content elsewhere and not need you any more – users still want and need things designed well, this just means that if your design works for the user for a particular type of content, they'll use it for any content of that type. I'd much rather watch youtube videos using vimeo's layout than youtube's. Actually, I'd much rather have vimeo's comments, too.

    We're still quite a way off the average user being able to see whatever they want however they want it but these technologies and tools are definitely heading that way. I just wish I'd made a bet on it way back when.

    Comments

Browse articles by category:

Toys, Guides, Opinion, Geek, Non-geek, Development, Design, CSS, JS, Open-source Ideas, Cartoons, Photos

Recent side-projects

  • Insta-Art
  • 8-Bit Alpha
  • Shelvi.st
  • The Elementals
  • Harmonious

Toys

Find my digital toys at thelab.thingsinjars.com.

Contact

Send me a message via Twitter.

Adopt-a-Museum

@thingsinjars:

    © 2012 Simon Madine