{"componentChunkName":"component---src-templates-project-detail-js","path":"/project/npr-songs-we-love/","result":{"data":{"site":{"siteMetadata":{"host":"xenodochial-pasteur-bb9d87.netlify.com"}},"markdownRemark":{"html":"<p>NPR curates an excellent \"songs we love\" list, but in 2014 they went above and beyond by releasing a <a href=\"https://apps.npr.org/best-songs-2014/\">web app for their full 2014 Songs We Love setlist</a>. A bunch of coworkers and myself set out to listen to every single track in the list, all 300. We all ran into the same problem though: despite the app having links to music services like Spotify, Amazon, and the late Rdio, we would lose track of the songs we personally loved. The app desperately needed some sort of favoriting feature.</p>\n<p>After going to <a href=\"https://2014.cascadiajs.com/\">CascadiaJS</a> in 2014 and <a href=\"https://www.youtube.com/watch?v=h-BzNxuu2yw\">seeing Lydia's talk on bookmarklets</a>, I accepted an optional life quest to make a useful bookmarklet. With this Songs We Love app missing a favoriting feature, now was my chance.</p>\n<h2>Bookmarklets</h2>\n<p>Bookmarklets are a form of bookmark that instead of linking to a URL links to a JavaScript snippet, using the <code class=\"language-text\">javascript:</code> protocol. This is mostly a vestigial feature of the Old Web, but since things don't get deprecated on the web, we can still use it today. A very simple bookmarklet looks something like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\">javascript<span class=\"token operator\">:</span><span class=\"token function\">alert</span><span class=\"token punctuation\">(</span><span class=\"token string\">'Hello world'</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>For the most part, the JavaScript to the right of the <code class=\"language-text\">javascript:</code> protocol can be anything inclosed in an iife, but there are caveats. The biggest one being that everything needs to be a single line.</p>\n<p>But it's not like code has to be authored in a single line, it just needs to be delivered in one line. So I <a href=\"https://github.com/DingoEatingFuzz/npr-music-we-love-bookmarklets/blob/master/index.js\">wrote a build step</a> that reads a file, removes new lines, wraps the code in an iife, and prepends the protocol.</p>\n<h2>Hearting tracks</h2>\n<p>To \"heart\" a track, a user just needs to click their \"NPR Heart\" bookmarklet in their bookmark bar. This is convenient because it means the UI of the NPR app doesn't need to be manipulated.</p>\n<p>The bookmarklet runs in the same context as the current web page, which means it gets to leverage any globally accessible code the web page has. This can be dangerous of a bookmarklet is expected to be run on any number of websites, but since these were specifically meant for this app, I could safely use jQuery.</p>\n<p>I used jQuery to scrape pertinent data from the \"now playing\" section of the UI. This included basics like track name and artist name as well as additional useful information such as streaming service links.</p>\n<p>Next this object needed to be persisted to local storage. Again, since this bookmarklet runs in the same context as the current web page, the bookmarklet can also access local storage in the same way the app does. So persisting a hearted track meant parsing the existing heart list, appending the new track, and setting the new stringified array.</p>\n<p>At this point, the user workflow is to</p>\n<ol>\n<li>Use the app as directed</li>\n<li>Click the NPR Heart button when the current song playing is good</li>\n</ol>\n<p>So far this works perfectly in that the track is persisted to local storage, but there is no indication that this worked at all. To add some indication, I used jQuery again to temporarily attach an absolutely positioned image.</p>\n<h2>Displaying hearted tracks</h2>\n<p>Having the list of hearted tracks in local storage doesn't do much good if there is no way to see the list. I made another bookmarket for printing out the list by leveraging the existing styles of the app. Using jQuery to add elements to the DOM is easy, but templating the list in the context of a bookmarklet was a much taller order.</p>\n<p>I've always been a fan of mustache and handlebars, but I wasn't about to add a dependency to a bookmarklet. Instead I wrote my own minimum viable template compiler.</p>\n<p>It used a simple syntax for handling value substitions and conditionals. Since ES5 doesn't have good multiline strings, I instead used the <code class=\"language-text\">Function#toString</code> trick. Put it all together, and the most complicated template looks like this in source:</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">var</span> trackTemplate <span class=\"token operator\">=</span> <span class=\"token function\">template</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span><span class=\"token comment\">/*\n  &lt;div class='song small' style='background:#0B0E13;border-bottom:2px solid #2F4C5A;'>\n    &lt;div class='container-fluid'>\n      &lt;div class='song-info'>\n        &lt;div class='song-info-wrapper'>\n          &lt;h2 class='artist'>{artist} &lt;span class='tag-header'>(from {contributor})&lt;/span>&lt;/h2>\n          &lt;h1 style='color:white' class='song-title'>{song}&lt;/h1>\n          &lt;ul class='song-tools list-unstyled'>\n            [links.amazon? &lt;li>&lt;a target='_blank' href='{links.amazon}' class='amazon'>&lt;span class='icon-amazon'>&lt;/span>&lt;/a> ]\n            [links.itunes? &lt;li>&lt;a target='_blank' href='{links.itunes}' class='itunes'>&lt;span class='icon-apple'>&lt;/span>&lt;/a> ]\n            [links.rdio? &lt;li>&lt;a target='_blank' href='{links.rdio}' class='rdio'>&lt;span class='icon-rdio'>&lt;/span>&lt;/a> ]\n            [links.spotify? &lt;li>&lt;a target='_blank' href='{links.spotify}' class='spotify'>&lt;span class='icon-spotify-circled'>&lt;/span>&lt;/a> ]\n          &lt;/ul>\n        &lt;/div>\n      &lt;/div>\n    &lt;/div>\n  &lt;/div>\n*/</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>The <code class=\"language-text\">template</code> function just calls <code class=\"language-text\">toString</code> on the first argument and extracts the comment body. Within the comment body are two non-html language features: variables in the form of <code class=\"language-text\">{variable}</code>, and conditional in the form of <code class=\"language-text\">[boolean? &lt;print if true&gt;]</code>.</p>\n<p>There are a lot of problems with this syntax if I was proposing a general purpose templating language, but I'm not. This was my own tiny little DSL. The \"compiler\" is barely 20 lines:</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">function</span> <span class=\"token function\">templateCompile</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">template<span class=\"token punctuation\">,</span> props</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">/* handle conditionals */</span>\n  template <span class=\"token operator\">=</span> template<span class=\"token punctuation\">.</span><span class=\"token function\">replace</span><span class=\"token punctuation\">(</span><span class=\"token regex\"><span class=\"token regex-delimiter\">/</span><span class=\"token regex-source language-regex\">\\[(.+?)\\?(.*?)\\]</span><span class=\"token regex-delimiter\">/</span><span class=\"token regex-flags\">g</span></span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">str<span class=\"token punctuation\">,</span> prop<span class=\"token punctuation\">,</span> tmpl</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token function\">getProp</span><span class=\"token punctuation\">(</span>props<span class=\"token punctuation\">,</span> prop<span class=\"token punctuation\">)</span> <span class=\"token operator\">?</span> tmpl <span class=\"token operator\">:</span> <span class=\"token string\">''</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token comment\">/* handle substitutions */</span>\n  template <span class=\"token operator\">=</span> template<span class=\"token punctuation\">.</span><span class=\"token function\">replace</span><span class=\"token punctuation\">(</span><span class=\"token regex\"><span class=\"token regex-delimiter\">/</span><span class=\"token regex-source language-regex\">\\{(.+?)\\}</span><span class=\"token regex-delimiter\">/</span><span class=\"token regex-flags\">g</span></span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">str<span class=\"token punctuation\">,</span> prop</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token function\">getProp</span><span class=\"token punctuation\">(</span>props<span class=\"token punctuation\">,</span> prop<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">return</span> template<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">function</span> <span class=\"token function\">getProp</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">obj<span class=\"token punctuation\">,</span> prop</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">var</span> propChain <span class=\"token operator\">=</span> prop<span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token string\">'.'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">var</span> val <span class=\"token operator\">=</span> obj<span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">for</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">var</span> i <span class=\"token operator\">=</span> <span class=\"token number\">0</span><span class=\"token punctuation\">,</span> len <span class=\"token operator\">=</span> propChain<span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">;</span> i <span class=\"token operator\">&lt;</span> len<span class=\"token punctuation\">;</span> i<span class=\"token operator\">++</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">var</span> newVal <span class=\"token operator\">=</span> val<span class=\"token punctuation\">[</span>propChain<span class=\"token punctuation\">[</span>i<span class=\"token punctuation\">]</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>newVal<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      val <span class=\"token operator\">=</span> val<span class=\"token punctuation\">[</span>propChain<span class=\"token punctuation\">[</span>i<span class=\"token punctuation\">]</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">return</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">return</span> val<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h2>Deploying the bookmarklets</h2>\n<p>The way people typically add bookmarklets is by dragging links from a web page into their bookmarks bar. So the deploy artifact for this project was an html page that contained the bookmarklet links and instructs for \"installing\" the bookmarklets.</p>\n<p>This project was well received by a couple coworkers, and that was more than enough to make the project worth it.</p>\n<h2>Technology used</h2>\n<ul>\n<li>JavaScript</li>\n<li>jQuery</li>\n<li>Bookmarklets</li>\n</ul>","fields":{"id":"npr-songs-we-love"}},"dataProjectsToml":{"projects":[{"name":"Nomad Web UI","slug":"nomad-web-ui","tags":["JavaScript","EmberJS","Cluster Scheduler","DevOps","Work"],"url":"https://nomadproject.io","year":2018,"thumbnail":"nomad-ui.png","description":"The Web UI for the Nomad cluster scheduler developed and maintained by HashiCorp. Cluster schedulers are tools designed to take arbitrary workloads and run them on arbitrary computers in a cluster. The UI helps operators in an organization maintain availability of services and computation while monitoring resources across the cluster. The UI also helps developers deploy their own services without needing the expertise operators sepcialize in.\n"},{"name":"HashiConf Generative Art Plotter","slug":"hashiconf-genart-plotter","tags":["Processing","Generative Art","SVG"],"url":null,"year":2018,"thumbnail":"hashiconf-genart-plotter.jpg","description":"An art installation at HashiConf 2018. Throughout the two conference days, the 2D plotter and a preview monitor were installed in the HashiCafe for attendees to watch and (if they were lucky) take home a one-of-a-kind keepsake.\n"},{"name":"CIVIC Platform","slug":"civic-platform","tags":["JavaScript","Data Visualization","Python","Leadership","Volunteering"],"url":"https://civicplatform.org","year":2018,"thumbnail":"civic-platform.png","description":"The CIVIC Platform is the flagship product from the CIVIC Software foundation: a non-profit I am the volunteer CTO of. The platform facilitates a data pipeline, moving public data into structured, queryable databases in the cloud. It exposes numerous APIs for building tools and stories with public data. It also comes with a web application that curates stories in the form of cards to show insights that are consumable by any citizen.\n"},{"name":"U.S. in Water","slug":"us-in-water","tags":["JavaScript","Cartography","Tippecanoe","GCP"],"url":"http://stuff.mlange.io/wc-final","year":2017,"thumbnail":"us-in-water.png","description":"A detailed look at all the rivers, streams, and bodies of water in the United States as tracked in the <a href=\"https://nhd.usgs.gov/\">USGS National Hydrography Dataset</a>. Many gigabytes of data were converted into vector tiles using Mapbox's Tippecanoe tool.\n"},{"name":"Climb Tracker","slug":"climb-tracker","tags":["JavaScript","React","Firebase"],"url":"https://climb.mlange.io","year":2016,"thumbnail":"ct-tracker-thumb.png","description":"A simple web app for tracking bouldering workouts. It focuses on quickly marking which problems were completed and which were attempted. In this way, at the end of the workout, a histogram and tally of problems and problem difficulties is plotted. There are also monthly reports to look back on.\n"},{"name":"Emoji Skin Tone Randomizer","slug":"emoji-skin-tone-randomizer","tags":["JavaScript","Chrome"],"url":"https://github.com/DingoEatingFuzz/chrome-emoji-skin-tone-randomizer","year":2016,"thumbnail":"emoji-skin-tones.png","description":"A chrome extension for assigning a random skin tone to a skin tone eligible emoji that doesn't already have one assigned.\n"},{"name":"Headers Middleman","slug":"headers-middleman","tags":["JavaScript","React","Chrome"],"url":"https://github.com/DingoEatingFuzz/chrome-headers-middleman","year":2015,"thumbnail":"headers-middle-man.png","description":"A chrome extension for modifying the headers of HTTP Requests based on regex pattern matching.\n"},{"name":"NPR Songs We Love Bookmarklets","slug":"npr-songs-we-love","tags":["JavaScript","Browsers"],"url":"https://github.com/DingoEatingFuzz/npr-music-we-love-bookmarklets","year":2015,"thumbnail":"npr-songs-we-love.png","description":"The NPR Songs We Love app was a wonderful thing, but it had no way to pin/favorite/star/save the tracks you liked. This was solvable in many ways, but the way that sounded the most interesting at the time was bookmarklets. With a click of a bookmarklet, the track would be saved to localstorage. I went overboard and created additional bookmarklets for seeing the track list and for disliking tracks. You know, just for fun.\n"},{"name":"Github Avatar Arrangement","slug":"github-avatar-arrangement","tags":["Python","Processing"],"url":"https://github.com/DingoEatingFuzz/github-gravatars","year":2014,"thumbnail":"github-gravatars.png","description":"A quick sketch that generates all possible Github Avatars (not including color variations). Since Github default avatars are created through the toggling of 15 states, the resulting space is only 2<sup>15</sup>. Which is high, but not so high it isn't presentable in a single image.\n"}],"images":[{"project":"hashiconf-genart-plotter","url":"/images/hashiconf-genart-plotter.jpg","alt":"A finished plot of the Consul product","caption":"Each of the six HashiCorp products can potentially be plotted via the generative art algorithm. This is an example of the Consul product, which is the only product\ngrid that features circles.\n"},{"project":"hashiconf-genart-plotter","url":"/images/hashiconf-genart-plotter-nomad.jpg","alt":"A finished plot of the Nomad product","caption":"This is an example of the Nomad product. Each plot is unique, so although each product grid is typically very well ordered, here they are not.\nUsing perlin noise, some lines are dropped, spaced irregularly, and slightly rotated.\n"},{"project":"hashiconf-genart-plotter","url":"/images/hashiconf-genart-plotter-terraform.jpg","alt":"A finished plot of the Terraform product","caption":"To prevent the art from going to abstract, each plot includes the product logo, stylized in a way that complements the line art a plotter produces.\nThen, to tie the plot to the event, the latest product version number is plotted to hopefully invoke nostalgia years later.\n"},{"project":"us-in-water","url":"/images/us-in-water-fullscreen.png","alt":"A fullscreen seenshot of the U.S. in Water project","caption":"The project is presented as a minimalist map with a set of clickable features on the left-hand side. The features on the left are hand-picked coordinates\nthat I think are interesting to look at.\n"},{"project":"us-in-water","url":"/images/us-in-water-central-california.png","alt":"Central California in the U.S. in Water map","caption":"Central California has well-documented flowlines, which make for a rich picture of the passage of water.\n"},{"project":"us-in-water","url":"/images/us-in-water-louisiana.png","alt":"Louisiana in the U.S. in Water map","caption":"As the state sinks and eordes while the gulf rises, the total land area of Luisiana is shrinking.\n"},{"project":"us-in-water","url":"/images/us-in-water-mt-hood.png","alt":"Mt. Hood in the U.S. in Water map","caption":"The shape of Mt. Hood is distinct through the features of glaciers and lakes alone.\n"},{"project":"us-in-water","url":"/images/us-in-water-salton-sea.png","alt":"Salton Sea in the U.S. in Water map","caption":"The Salton Sea is the biggest lake in California. Evident in the water lines, there are massive-scale feats of engineering surrounding the lake.\n"},{"project":"civic-platform","url":"/images/civic-platform-overview.png","alt":"The CIVIC Platform home page","caption":"The CIVIC Platform website is a gateway to a suite of story cards that each provide an interactive tool or insight that lets people understand their city a little bit better.\n"},{"project":"civic-platform","url":"/images/civic-platform-housing.png","alt":"A story card in the 2018 housing collection","caption":"All StoryCards aim to have some explanation, some data visualization, and some interactivity.\n"},{"project":"civic-platform","url":"/images/civic-platform-disaster.png","alt":"A multivariate plot chart in the 2018 disaster collection","caption":"The frontend <a href=\"https://github.com/hackoregon/civic\" target=\"_blank\" />civic frontend repo</a> provides a collection of a UI components that enable quickly making rich and interactive visual explanations.\n"},{"project":"civic-platform","url":"/images/civic-platform-sandbox.png","alt":"The CIVIC platform sandbox","caption":"The CIVIC sandbox is a feature of the CIVIC platform that gives people a chance to dive deeper into data without having to make the jump into developer tools to run databases and notebooks locally.\n"},{"project":"climb-tracker","url":"/images/ct-home.png","alt":"The landing page for the Climb Tracker app","caption":"A straight-forward page that markets some features and requires auth. The background photo I took in Zion National Park.\n"},{"project":"climb-tracker","url":"/images/ct-tracker-blank.png","alt":"The Climb Tracker tracker with nothing tracked yet","caption":"This is the screen you see after authenticating. It is the tracker, which is the primary experience. It has two features which are immediately\nidentifiable: a list of colored buttons for tracking a climb of a difficulty, and an undo button in the event you track something in error.\nThe third less obvious feature is the \"A\" button, which marks attempts at problems.\n"},{"project":"climb-tracker","url":"/images/ct-tracker-full.png","alt":"The Climb Tracker tracker with various climbs tracked","caption":"As you use the climb tracker throughout your session, a histogram is formed that makes it very clear which difficulties you are focusing on.\nThis can be used to tell you when your warmup is done, if you're pushing yourself too hard and maybe that's why you aren't finishing anything, or\nsimply, what your success rate per difficulty is. The stripe-textured regions denote attempts.\n"},{"project":"climb-tracker","url":"/images/ct-reports.png","alt":"The Climb Tracker reports section","caption":"The reports section gives you a detailed memory of your climbing progress and frequency over time. The dashboard metrics at the top\nare useful for quick stats for the current month. Proceeding the dashboard metrics are monthly report cards that feature kabob\ncharts to illustrate difficulty histograms over time.\n"},{"project":"headers-middleman","url":"/images/headers-middle-man-large.png","alt":"Headers Middleman options page","caption":"The options page for Headers Middleman. Used to add \"rules\", which are regular expressions that match URLs, and \"headers\" which can be values\nfor new headers, values to override headers, or removing an unwanted header.\n"},{"project":"npr-songs-we-love","url":"/images/songs-we-love-love.png","alt":"NPR Songs We Love love indication","caption":"Since clicking a bookmark to add an entry to local storage gives no feedback to the user, this heart is flashed on the screen. It's just slapped\ninto the DOM with some jQuery.\n"},{"project":"npr-songs-we-love","url":"/images/songs-we-love-hate.png","alt":"NPR Songs We Love hate indication","caption":"Hate is a nearly identical bookmark to Love. The only differences are the local storage key and the hotlinked icon URL.\n"},{"project":"npr-songs-we-love","url":"/images/songs-we-love-list.png","alt":"NPR Songs We Love love list","caption":"The Love List bookmarklet acts as a toggle. If the Love List element is found on the page, it's removed, otherwise it's added. This ended up being\na tricky bookmarklet since it contains full blown templating for binding the loved track data. Included in this project write up is\na sample of what that templating looks like.\n"},{"project":"npr-songs-we-love","url":"/images/songs-we-love-bookmarklets.png","alt":"NPR Songs We Love bookmarklet page","caption":"This was the quick and dirty \"installation\" page. A user interested in this augmentation to the Songs We Love 2014 app would drag these links\ninto their bookmark bar. The trick being that they aren't your typical link. They take the form of <code>javascript:&lt;insert-lots-of-javascript-here&gt;</code>.\n"},{"project":"nomad-web-ui","url":"/images/nomad-ui-jobs.png","alt":"Nomad UI jobs list","caption":"The Jobs List page serves as the home page for developers interacting with Nomad. It represents all software known to the cluster.\n"},{"project":"nomad-web-ui","url":"/images/nomad-ui-deployments.png","alt":"Nomad UI job deployments","caption":"Nomad supports automatic rolling and green/blue deployments as well as optional canary deployments. All of this is represented in the UI\nand kept up to date in realtime.\n"},{"project":"nomad-web-ui","url":"/images/nomad-ui-logs.png","alt":"Nomad UI stdout log streaming","caption":"Nomad also has a streaming HTTP API for tailing logs for allocations. The UI lets any privileged user see these logs using fetch and streaming\nrequests when possible and falling back to polling otherwise.\n"},{"project":"nomad-web-ui","url":"/images/nomad-ui-stats.png","alt":"Nomad UI metrics over time","caption":"Stats in Nomad are not stored, but the UI makes a best effort attempt at tracking data over time by storing previous values in memory,\naccounting for skipped data, and persisting historical data (up to a limit) between page views.\n"}]}},"pageContext":{"slug":"/project/npr-songs-we-love/"}},"staticQueryHashes":[]}