Last year I wrote about a tiny annual calendar I made to track habits and map out the year ahead. Over the weekend I created a new version with some additional options, online here. This post will cover some of the things I learned making it.
I've added date widgets to web apps before, but always from a UI library. I didn't realize until this weekend that HTML has <input type="date" />
. It's good enough for my use, and it works well on mobile.
At one point while making this version of Tinycal, I toyed with making the calendar get larger as it got shorter, including the size of the numbers. I've built a lot of SVGs that include text, and whenever I needed to center text vertically I set set its "dy"
attribute to about half the font size, then tweaked the value until it looked right. But that wasn't so easy when the font size was dynamic. To solve that problem, I found that <text>
has a "dominant-baseline"
attribute that shifts tex vertically. A value of "middle"
was a little too high, "central"
was better, though still a bit higher than I would have done by hand.
Out of habit I used cljs-time for the date math, but I ran into an open bug in how days are counted across the start of daylight savings. Looking for an alternative, I discovered that cljc.java-time supports ClojureScript. The should have been obvious to me from the name, but I've only ever used it from the Clojure side, and the API is so Java-oriented that it never occurred to me to try it with ClojureScript until pressed. For the little date math I needed, it was easy to switch from cljs-time to cljc.java-time, and it was refreshing to have the same API I was used to from Clojure.
Years ago I made a JavaScript library to download SVGs as PNGs from the browser. It converts the SVG to a data URI, creates an image from it, draws the image to a canvas, then converts the canvas to a PNG data URI. While that works, MDN warns that toDataURL()
might not work for large images and suggests using toBlob()
instead. That turned out to be much simpler and more direct. If you want details, the code is here.
With the previous version of Tinycal I intended for users to produce a PDF by using the browser's print dialog. When, a year after I'd made the app, someone asked me how to download the calendar, I'd forgotten, and I had to spent a minute remembering. To avoid that confusion with this version, I added a clear "Print" button, and to make it work learned about JavaScript's window.print()
.
On another SVG project I used PDFKit to export a PDF of several SVGs. In that code I loaded pdfkit.standalone.js
in an HTML <script/>
tag and not in the source ClojureScript code, and I didn't know why. Trying to rectify that, though, I may have found the reason. I got an error because of a PDFKit dependency, iconkit, which has a duplicate object key, which Google Closure won't compile under strict mode. Maybe there's a better workaround (disabling strict mode?), but for expediency I followed my previous course and just added the standalone JS to the HTML. That added half a megabyte of JavaScript to the page load, so to avoid that I only added the <script/>
tag when the "Download PDF" button is clicked.
I wanted to use an external SVG library but test local changes before committing them. Clojure's deps.edn supports local dependencies with :local/root
, and it allows adding an alias with :override-deps
, but I had some trouble figuring out how to get shadow-cljs to use it without using it all the time. By accident I found shadow-cljs's -A
option, which allows specifying additional aliases to include when running.