Tagebuch: Dashboard

The last few days were crazy :star_struck:

I got accepted at a Usability Testessen („usability test dinner“) event to present this project and let other people test it. This meant: a lot of cleaning up and polishing. :broom:

First, there are two new widgets: QR Code and Counter. I wanted to do some dogfooding with the QR widget, i.e., use a second laptop at the event to display a QR code leading to the Dashboard website. In the end I just printed the QR code on a sheet of paper :sweat_smile:

I’ve spent some time improving the user experience. There is a new welcome page when the Dashboard is being used for the first time (or rather when there are no widgets configured). A demo/template is still available, if the user chooses to use one. The widgets now show an icon if some configuration is required (e.g. a missing image URL). I’ve even improved the „JavaScript required“ page for all the privacy-conscious people out there.

1.1.3b

Finally, I’ve fixed some small bugs (broken widget drawer on mobile, selected language not persisted on page reload), and implemented a big performance improvement (widgets are not re-mounted each time the layout changes anymore).

I’m really excited and looking forward to getting some valuable feedback from other people. Tomorrow’s the day. :blush:

Changelog

  • :sparkles: New widget: QR Code.
  • :sparkles: New widget: Counter.
  • :sparkles: Display a message if required widget configuration is missing.
  • :sparkles: Display a welcome page when no widget is available.
  • :rocket: Move the board edit button into the header.
  • :rocket: Display a list of examples within the Website widget configuration.
  • :rocket: Improve widget buttons design.
  • :rocket: Provide a more detailed message if the user disabled JavaScript.
  • :rocket: Prevent re-mounting all widgets on every layout change.
  • :bug: Persist the selected language across sessions.
  • :bug: Fix widget drawer layout on mobile.
  • :gem: Refactor form components.
  • :hammer: Add new file generators.
  • :book: Update documentation.

Usability Dinner

As mentioned in my previous post, I have been working hard to polish this project for the Usability Testessen. This event also forced me to concentrate more on the marketing part, i.e., creating a pitch and rethinking the target groups / use cases.

In 6 testing sessions (12 minutes each), 11 people gave me lots of feedback and ideas. Some issues were obvious, and I already knew I have to fix them (e.g. not being able to configure a widget in „edit“ mode). Some issues I didn’t realize (e.g. „How do I move a widget?“). And some ideas I didn’t even consider until now (e.g. a dashboard background image).

The overall excitement for my Dashboard project wasn’t overwhelming (maybe because I was the only non-startup at the event). However, one feature everyone loved was the Dark Mode. Don’t ever underestimate this.

Project update

The UI design is rather pragmatic so far. I want it to stay minimalistic, but it doesn’t have to look so boring. I’ve started applying some principles from Refactoring UI by introducing different font sizes and font weights. This alone makes the Date&Time and Chemical Elements widgets look so much better.

The widget drawer needs a major UI/UX redesign. The first step was to group all widgets by category.

This release contains some small bug fixes and improvements as well. One interesting thing I have learned is the inconsistent button behavior among browsers. This affected the Input clear button on macOS, which is now fixed.

Finally, I’ve increased the number of columns from 12 to 24 and adjusted the row height to provide a more precise widget sizing.

There was one more idea I’ve played with but abandoned for now: fluid typography. Currently, the font sizes are pretty much fixed. The user has to resize the widget to a size where all the content will fit into. I thought it would be nice to have the content match the widget size instead. However, there are some issues with this idea:

  • There is no native CSS way to achieve this. The best workaround I have found was to use transform: scale() (cross-browser version for zoom so to say). But it’s fragile and requires some pixel-perfect adjustments.
  • Some widgets shouldn’t scale with the widget size (e.g. notes, website). One could turn off the scaling for selected widgets, but then the look&feel wouldn’t be as consistent anymore.
  • The overall motivation for this idea is to enable the user to see a larger version of some widgets. That is already possible by using the browser zoom feature. I should probably improve the UX by adding some buttons to control the zoom level.

Changelog

  • :rocket: Introduce widget categories.
  • :rocket: Use 24 columns instead of 12.
  • :rocket: Center template board horizontally.
  • :rocket: Use a red trash icon for the widget removal button.
  • :rocket: Let the header link point to home instead of GitHub.
  • :rocket: [Chemical Elements]: Display German name if German language is active.
  • :bug: [Image]: Let the content always fit the whole widget.
  • :bug: Fix the Input clear button on macOS.
  • :gem: Use consistent typography.
  • :gem: Add borderless buttons.
  • :gem: Use the public Font Awesome icon package.
  • :book: Update documentation.

Edit mode redesign

8/10 of my testers found the „edit mode“ confusing and unnecessary. From user perspective, I agree: why can’t I just drag and drop the widgets without entering an „edit mode“? The challenge was to make it work reliably without sacrificing the simple UI. For example, I like the Text widget’s full-size behavior, which makes implementing drag’n’drop more difficult, though.

One common pattern is to use a fixed header for every widget and use it as a drag handler. I’ve tested this approach, but I really dislike cluttering the UI with otherwise unnecessary headers.

Instead, I’ve got inspired by Ryeboard. All widgets contain an overlay that makes them movable/resizable by default. To access the widget itself, the user has to click it once (without dragging). I think this solution has the best trade-off between good UX and simple UI.

Other changes

This release contains several small UI improvements:

  • After the edit mode redesign, I’ve also reworked the widget button bar and made it keyboard-accessible.
  • Configuration modals will now be saved on pressing Enter within any input field.
  • When starting Dashboard for the first time, the default theme (light/dark) will depend on the user’s system preference.
  • Moving widgets around had often lead to a mess. As a workaround, I’ve activated a „vertical compact mode“. This means, widgets will „stick“ vertically, leaving no gaps. While it makes the widget placement more strict, it will lead to less overall frustration.

Apart from that, I’ve been doing lots of maintenance and code improvement stuff:

  • I’ve extracted shared styles into a separate package. This was inspired by Kent C. Dodds’ blog post.
  • I’ve included some more sanitize.css rules. For example, Buttons now use the same font as the rest of the app.
  • I did a big color palette refactoring. If you don’t see any difference, I did a good job. In summary, I’m now using colors from the U.S. Web Design System, which have a very special accessibility property. It is a big and interesting topic on its own, which screams for a separate blog post.
  • I’ve also spent some time making the focus indicator accessible everywhere. I’ve summarized my lessons learned in a new blog post.
  • A new react-grid-layout release brings some great improvements. Mainly, the widgets don’t rerender anymore on breakpoint changes (so widget state is persisted). Unfortunately, it has another critical bug. I’ve provided a fix, but it may take some time to get it merged and released, so I’ve forked the project. Fortunately, npm makes it trivial to replace any package with a GitHub URL.

Changelog

  • :sparkles: Enable moving/resizing widgets in view mode.
  • :rocket: Enable widget configuration in edit mode.
  • :rocket: Save configuration modals on pressing Enter within inputs.
  • :rocket: Detect OS dark theme preference.
  • :rocket: Improve widget placement by removing vertical gaps.
  • :rocket: [Text]: Display a focus indicator.
  • :bug: Persist widget state on viewport changes.
  • :gem: Add semantics to all input fields.
  • :gem: Provide focus ring fallback for Windows High Contrast mode.
  • :gem: Normalize styling across browsers.
  • :gem: Extract shared styles into a separate package.
  • :gem: Use a common color palette.
  • :gem: Replace the (hamburger) menu button with a custom Button component.
  • :hammer: Update dependencies.

1.2.0.30e32db4

Server module

I’ve tried to develop the Dashboard as a client-only web application for as long as possible. But now that I’ve started including 3rd party data dependencies, I was experiencing CORS issues. Jup, you cannot fetch many resources (like calendar feeds) directly, but you can write a server and just pass through the request. I understand the security implications, but from DX (developer experience), this is inconvenient.

Of course, I knew that I will have to write a server someday (e.g. to „hide“ private keys for 3rd party APIs or to implement a login feature). I just didn’t expect it to happen so soon. But maybe it’s the perfect timing since Apple is killing PWAs.

Cryptocurrencies widget

cryptocurrencies.f3cb7dce

The first widget that uses the new server module is Cryptocurrencies. It displays the current market price and its 24h trend. The data updates automatically every 15 minutes. It uses the awesome CoinGecko API.

Backup & Restore

It’s now possible to export&import all user data. This enables multiple things:

  1. Data backup. The data is your and every user has the right to download it.
  2. Manual syncing user data across computers (until the new server module provides a more user-friendly alternative).
  3. Create and save different use cases. That’s nice both for development and showcases.

Maintenance

The changelog looks small, but a lot refactorings and improvements happened under the hood.

Amongst others, I’ve split the project into 3 independent modules: app, server and docs. This led to a new blog post for configuring GitLab CI for monorepos.

Changelog

  • :sparkles: New widget: Cryptocurrencies.
  • :sparkles: Enable data backup & restore.
  • :rocket: Make input clear button not focusable via keyboard.
  • :hammer: Split project into separate modules.
  • :hammer: Create an Express server.
  • :hammer: Update dependencies.

New widget: GitHub Stats

There’s a new widget for displaying GitHub stats for a user or a repository :tada:.

github-stats

The main work happened on the backend side. Setting up a unit test infrastructure with jest and supertest was more difficult than expected, mainly getting the axios/cachios mocking right. But it was worth it: Test Driven Development saved me twice already :blush:.

Fullscreen feature

When viewing a dashboard on a dedicated screen, one usually doesn’t want to see the browser UI around it. It was the first time I’ve learned about the Fullscreen API. Previously, I thought there was a JavaScript function to just trigger the F11 native browser fullscreen mode. But the API is more powerful than that - it can put any DOM mode in fullscreen. It still has some quirks, but react-full-screen abstracts everything away and JustWorks™.

Contributing

This project got the first contribution: a French translation, provided by (@faboo03)! Merci beaucoup :grinning:.

To encourage more people to contribute, I’ve improved the documentation (incl. a rough roadmap), created a Gitter channel for communication and started moving some tasks from my personal TODO list into Github Issues. I’m happy to see this project moving forward!

Request caching

While the Coingecko API is very generous (100 requests/min with no registration), this project shouldn’t abuse it. An obvious optimization is caching external requests on the server side.

I am using axios, a popular HTTP client library, so it wasn’t surprising to find multiple caching modules:

While evaluating I’ve created a list of requirements:

  • Cache TTL (time to live) must be configurable per request (because of different update cycles per widget type).
  • Parametrized caching (i.e., /moo?v=1 and /moo?v=2 should be cached separately).
  • In-memory cache is sufficient for now, but using a scalable alternative should be technically possible.
  • Store only relevant data instead of the whole axios response.
  • Logging (to verify the functionality and debugging).
  • TypeScript support.

No library fulfills all requirements, but cachios comes close. It doesn’t provide logging, but it’s possible to use axios interceptors to log the actual requests being sent. If the interceptor doesn’t fire, the response is coming from the cache. Cachios also doesn’t come with TypeScript support, but creating a custom definition file (at least for my use cases) wasn’t difficult.

So here we are: now the API will only be called once per currency/cryptocurrency pair every 15 minutes.

In the future I might add client-side caching (i.e. caching internal API requests), too, but for now it’s not necessary.

Changelog

  • :sparkles: New widget: GitHub Stats.
  • :sparkles: Add French translation (@faboo03).
  • :sparkles: Add a button to view the dashboard in fullscreen mode.
  • :rocket: [Date & Time]: Localize date format.
  • :rocket: Replace language select buttons with a dropdown.
  • :rocket: Cache external axios calls.
  • :gem: Remove unnecessary React.memo calls.
  • :gem: Improve error screen design.
  • :gem: Improve icon name TypeScript typing.
  • :gem: Implement error handling for internal and 3rd-party requests.
  • :hammer: Add a script to find missing label translations.
  • :book: Publish a roadmap.
  • :book: Update documentation.

Der Beitrag ist vom Januar, und der Screenshot noch ein paar Monate älter, wie man am Bitcoin-Wert sehen kann :smiley:


It has been some time since the last release. Dashboard became my on-off project, as I’ve been working on some of my other ideas, like evaluatory.

One of the best things was to have some new contributors. With their help, Dashboard now includes some new widgets:

  • Country of the Day: Display information about world countries (from the Tip of the Day project.
  • Twitter Stats: Display various Twitter user stats.
  • YouTube Stats: Display various Youtube channel stats.
  • Random Image: Display a random image. Changes daily.

View the full changelog to see all the awesome changes.

Design update

Previously, the dashboard looked cluttered and unbalanced. I’ve adjusted the font sizes, included more colors and added some space. There’s also a new web font. I’ve once been an advocate for the system font stack, i.e., using native instead of web fonts. But after introducing the Nunito font to my blog this year, I’ve decided to also give the Dashboard more personality. I’ve chosen Assistant, a sans-serif font that comes with the right level of „condensation“.

I’m not a designer, but the UI looks now more harmonized.

Background image

Adding a custom background image wasn’t high on my priority list. But since I’ve implemented this four months ago, I don’t want to miss it again. You can specify the URL to any image you like, but I’ve handpicked five example images: /backgrounds/bg-01.jpg through /backgrounds/bg-05.jpg. I might provide a better UI to pick one of the example images in the future.

New widget: Chart / Graph

There’s a new widget for displaying line charts/graphs. The best thing is the flexibility. You can specify a URL to any JSON document and define custom JSON paths for the data to be displayed. I’ve built this widget to display my daily server access numbers, but you can use it for pretty much anything.

Widget enhancements

My team is working in different timezones, so I find it useful to know the local time of my colleagues. You can now configure a timezone within the Date & Time widget alongside a custom headline.

Furthermore, the Cryptocurrencies widget now includes Nano and Dogecoin. :rocket:

Type-safe API

This is one of the „under the hood“ topics, but I like it a lot. I’m now using tsoa to simplify the creation of backend routes. Tsoa provides an abstraction over Express and handles boilerplate code for things like request validation. It also lets you create a Swagger/OpenAPI file. With swagger-typescript-api, I can then automatically create a type-safe frontend API. Sounds like a lot of tooling, but now there’s no more guessing/looking up what my services expect and return. I’ve started writing a separate post on my blog with more details.

Next steps

Dashboard remains my toy project: I can try out patterns, libraries and tools and create something useful as a byproduct. I don’t have any specific timeline for this project, but there are two things I’d like to handle next:

  • Redesign the widget drawer. Depending on the screen resolution, the bottom widgets are rendered outside the drawer. I didn’t invest much time to fix it, as I’ve planned to redesign the whole thing anyway.
  • Create a weather widget. I have already finished the backend service a few months ago, but I’ve procrastinated with the frontend part.

Changelog

  • :sparkles: New widget: Chart / Graph.
  • :sparkles: Make background image configurable.
  • :rocket: [Date & Time] Add headline and timezone configuration.
  • :rocket: [Cryptocurrencies] Add Nano currency
  • :rocket: [Cryptocurrencies] Add Dogecoin currency.
  • :rocket: Display widget resize handle when focused.
  • :rocket: Reset outdated data when restoring a backup.
  • :rocket: Make theme selection keyboard-accessible.
  • :rocket: Close settings modal by pressing Enter within the input field.
  • :gem: Harmonize design.
  • :gem: Use a web font: Assistant.
  • :gem: Increase base widget height.
  • :gem: Add a decorative illustration to the welcome page.
  • :bug: Make widgets configurable on mobile again.
  • :bug: Recalculate grid width on toggling the widget drawer.
  • :hammer: Switch npm scripts to @darekkay/scripts.
  • :hammer: Update ESlint rules.
  • :hammer: Replace babel parser with ts-parser for ESlint.
  • :hammer: Replace redux-saga with typed-redux-saga to support latest TypeScript version.
  • :hammer: Migrate stories to the Component Story Format.
  • :hammer: Update dependencies.