Tagebuch: Dashboard

I haven’t made much functional progress in the last month as I’ve been mainly working on my other project: Tip of the Day. It is relevant to the dashboard, though, as I wish to connect both projects. The idea is to have a widget which displays a random tip every day (e.g. “country of the day”). I’ve also made some progress under the hood and made the text widget editable (the content is not stored yet, though).

There is a new widget for showing the current date and time. Additionally, the state is now persisted across the sessions. Under the hood I’ve added redux-observable to manage side effects and I’ve played around with the new React Hooks.

The first step for GUI-based dashboard customization is done: the widgets are now draggable/resizeable. The widget selection is still fixed, though, so I should implement a widget drawer soon.

I’ve also added a new Search widget. Right now the DuckDuckGo search engine is hard-coded, but in the future there will be a list of pre-defined providers and also the possibility to define a custom website.

Finally, I’ve replaced most of my (S)CSS files with a Functional CSS approach. I’ve used Tailwind CSS, one of the most popular utility-first CSS frameworks. I really enjoy it so far :slight_smile:

Notice: The state data is stored in the Local Storage, so you might have to clear it if you have opened the website in the past. This won’t be necessary when the project is out of the work-in-progress state of course.

The announced widget drawer is now implemented. This means, all available widgets (date/time, notes, web search) can be placed onto the dashboard and moved/resized within the UI. Removing the widgets is not yet possible, though.

I’ve added multi-language support. Currently English (default) and German are available. I’ve used i18next to compare it to the more popular react-intl which I’m using at work. So far it’s really nice to use.

I’ve also included Storybook for development, a tool to preview UI components. It helped me already to find and fix some small issues.

Animations are now disabled if the user prefers not to see any.

Every widget can now be removed from the Dashboard. The last missing part of personalization is a settings dialog, so the user can select another search engine for example.

As I had previously copied the Button component from my other projecs, it did not follow the new CSS variable approach to support different themes. The migration was not as trivial, because I could not use the darken and transparentize SCSS functions with CSS variables anymore. Converting the colors into HSL made the transition easier, though. Now Dashboard support different Button colors for the light and dark themes.

Now that travis-ci.com supports open source projects, I’ve migrated all my projects from travis-ci.org. The GitHub integration automatically checks each commit and PR and submits a report directly on GitHub. I’ve also played around with the new GitHub Actions after getting into the Beta. While it’s quite powerful, I still prefer Travis CI and GitLab CI due to the native yarn support.

The personalization concept is fully implemented: every widget can now define a configuration modal. The first (and currently only) widget is the Search widget, where the user is able to define the title and URL pattern. A list of predefined search engines is also available for a quicker access.

When the widgets are initially loaded, a loading spinner was being shown before. I have replaced this loading indicator with skeleton loading for a nicer look.

Now I can concentrate on implementing more widgets to make this project actually useful.

To declutter the main view, I have moved all control buttons into a (hamburger) menu and a settings modal. I’ve also improved the theme selection, so a preview for each theme is displayed. Something similar will be available for the language selection in the future.

Until now, the available widgets and their configurability were hard coded. With more widgets and widget properties (like initial options) coming, a dynamic way to list the widgets and their properties was needed. Now creating a new widget means just using the file generator ( yarn generate ) followed by a script to re-scan the available widgets ( yarn scan-widgets ).

One of the first components was Date&Time. I had overengineered it by implementing a global heartbeat and putting the tick value in the global state. I thought this would make implementing further interval components (e.g. countdown) and (re-)fetching data easier. While implementing the first remote data-driven widget it turned out not to be the best idea after all. Hence, I have removed the global heartbeat and replaced it with local state.

A new button in the settings modal makes it possible to delete all user data. This is especially useful in the early stage of development, as the main data schema may still change regularly.

Documentation

The project documentation is now hosted on my own domain. The GitHub Markdown preview is nice (and can still be used), but a separate site has some advantages:

  • It is easier to view and navigate (especially for non-tech people).
  • It makes it possible to maintain the project journal as a blog (with RSS support!).
  • It looks much better.

There are so many documentation platforms and I’ve spent way too much time finding the right one. Docusaurus seemed like the best solution, even with first-class support for a blog. But the configuration is tricky and the folder structure inconvenient. Some other static site generators did not support simple relative links, like [link](docs/README.md). In the end, I went with VuePress, which offers almost everything that I need out of the box. The RSS part is based on an unofficial plugin that I have customized to my needs.

The documentation itself is an ongoing work in progress, especially for the Widget section.

Dependencies

When doing my regular dependency update, I was pleasantly surprised:

  1. redux-starter-kit 1.0 was released. Among the changes, createAction now allows defining custom types, leading to a better TypeScript integration.
  2. react-grid-layout, the library I am using for the dashboard grid, got the first updates since 1.5 years.

Unfortunately, I experienced a strange issue, which crashed the application. The core issue was easy to identify and fix: React props are mutated in react-grid-layout, which is never a good idea. But this code hasn’t changed in a while. So what caused the issue to emerge now? As I have updated a lot of libraries, it took a while to find the responsible library. In the end it was the immer dependency update in redux-starter-kit, which freezes the state, leading to the react-grid-layout bug becoming visible.

Support for remote data

So far, all widgets were fully local, i.e., they did not require any remote data. This makes the possibilities rather limited. I have finally designed the architecture to support fetching remote data, and I have implemented the first widget as an example: Chemical Element of the Day. This widget displays the data from my Tip of the Day project. Other TotD widgets will join the list in the future.

I had to revisit some architectural decisions. First, I have replaced redux-observable with redux-saga. I was hoping to gain some more experience with RxJS (which I did), but it felt too complicated to implement the easiest fetch/success/error flow. I find the procedural way of redux-saga more natural and easier to understand.

Second, I have removed the shared state completely. It was meant as a performance optimization for widgets with shared data, like the new Chemical Elements widget. The drawback was having to handle the state at different places, making the implementation more complicated than necessary. Instead, now every widget has its own (sub-)state. I might implement some request-level caching in the future to prevent fetching the same resource twice.

Other changes

All input fields contain an „X“ button to clear the content. It was tricky to get the rendering and focus right, but it looks good now.

I’ve added a Code of Conduct to the project. While there are no contributions yet, it’s important to create a welcoming and inclusive atmosphere from the beginning.

JetBrains released the Grazie Plugin, and advanced spelling/grammar/styling checker, for all of their IDEs. I have enabled this plugin for Markdown files, and it helped me to solve some minor issues.

There’s a new widget for displaying images! The URL is configurable in the widget’s settings modal. Nothing fancy, but there’s finally some color on the Dashboard :art:.

I’ve also started working on a more complex widget (spoiler alert: it’s about weather). I’ve done some more refactorings and improvements along the way to make the development easier. This includes extracting some boilerplate code, fixing the saga model and updating/adding file generators. I’ve also enabled GitLab CI, so I get an early feedback before making an official release on GitHub.

Changelog

  • :sparkles: New widget: Image.
  • :construction_worker: Make Storybook widgets resizeable for improved UI tests.
  • :construction_worker: Include a robots.txt file.
  • :construction_worker: Add new file generators (widget story, widget properties).
  • :construction_worker: Enable GitLab CI.
  • :construction_worker: Update dependencies.
  • :book: Use an accessible accent color for the documentation.

Ich darf mein Projekt nächste Woche beim Usability Testessen vorstellen :partying_face: Ich bin schon sehr gespannt :slight_smile:

2 „Gefällt mir“

Hab ich noch nie von gehört, klingt aber interessant. Habs mir mal für KA als bookmark hinterlegt. Vielleicht geh ich da mal mit IndustryCity hin.

Ich seh auch gerade, dass das gerne von Firmen veranstaltet wird. U.u. bekomme ich ja unsere dazu, das mal auszurichten :slight_smile:

1 „Gefällt mir“

There is a new widget: Website (Iframe). This makes it possible to include websites or web applications without a designated widget. However, many websites do not allow to render their page in an iframe (via CSP or X-Frame-Options). Personally, I am using this widget to include Todoist and my own Etherpad instance.

Previously, changing a widget setting was reflected immediately, without a way to revert the change. Now, widget settings can be cancelled in the modal. This change does not apply to the general settings modal, yet.

The UI language is now auto-detected. If the browser uses a supported language (currently English and German), it will be used by default. The language can still be changed in the settings modal afterwards.

I’ve also improved the documentation. Among others, I have split the widget documentation into separate files. This will make it possible to link to a widget’s help page directly from the dashboard in the future.

The Storybook is now hosted.

Changelog

  • :sparkles: New widget: Website (Iframe).
  • :sparkles: Auto-detect default language.
  • :rocket: Add a button to cancel changed widget options.
  • :rocket: Remove inner focus ring in Firefox.
  • :rocket: Normalize some CSS rules across browsers.
  • :bug: Prevent double vertical scrollbar.
  • :gem: Refactor the menu into a compound component.
  • :hammer: Host Storybook.
  • :book: Add internationalization docs.
  • :book: Standardize the changelog format.

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.