Live Scoreboard for Thailand General Election 2562 (2019).
This web application is built using Gatsby. We chose to use Gatsby because:
- It sets up the required tooling, such as webpack, Babel, routing (etc.) for us.
- We can add new pages by adding a file in
src/pages
. No need to fiddle with router. - It has a comprehensive documentation, including a lot of recipes.
- A lot of things can be done just by installing Gatsby plugins, such as adding Google Fonts, Google Analytics.
0pdd helps us keep track of TODOs by converting @todo
markers in source code into GitHub issue. The created issue will be closed automatically when the corresponding @todo
marker is removed from the source code.
To add a todo, put a comment in the source code beginning with @todo
, followed by issue number. If there’s no associated issue, just use the catch-all issue #1. Example:
// @todo #1 Add Analytics, e.g. Google Analytics.
// Check out Gatsby docs for how to add analytics
// - Main Guide: https://www.gatsbyjs.org/docs/adding-analytics/
// - Google Analytics: https://www.gatsbyjs.org/docs/adding-analytics/#using-gatsby-plugin-google-analytics
// - Google Tag Manager: https://www.gatsbyjs.org/packages/gatsby-plugin-google-tagmanager/
Note that if the @todo
spans multiple lines, subsequent lines must be indented with 1 extra space. See @todo
formatting rules.
You need to install these tools first in order to develop this project:
- Node.js (10.x)
- Yarn
Before you can start the development server, you have to install the dependencies first.
yarn install
yarn develop
When you run the app for the first time, if it’s not yet time to count the election results, the application will be disabled and you will see a curtain with a countdown. To develop this website, you have to disable the curtain first.
- Go to
/dev
, e.g. http://localhost:8000/dev - Toggle the flag
ELECT_DISABLE_CURTAIN
Just add a new file in src/pages
— each .js
file becomes a new page automatically.
We use emotion to style the website. It allows us to keep the CSS code close to the component that uses it.
-
Instead of using inline
style
or CSS file, use thecss
prop with object styles. Example:function Component() { return ( <div css={{ fontSize: 18, [media(600)]: { fontSize: 20, }, }} /> ) }
Object styles are preferred over string styles because:
- VS Code can help autocomplete CSS properties and values.
- Doesn’t require importing the CSS helper (
import { css } from '@emotion/core'
). - Can be automatically formatted using Prettier.
Development of the UI is done using mobile-first approach. The main benefit is that it allows many component to be reused easily. Most components for mobile can be use as-is on the desktop (just position it in a way that makes sense), while usually components for desktop must be re-implemented for mobile from scratch.
This results in one simple rule: @media (max-width)
should not be used. Instead, put in the mobile styling first, then use @media (min-width)
to enhance the component for desktop.
When it come to pre-rendered React applications, there are two main approaches to responsive design: CSS-based and React-based. Each approach has its own pros and cons. We use both approaches in this project, its trade-offs are discussed below:
-
CSS-based. We render the same HTML, but use CSS to apply styling.
- Pro: The same markup can be shared.
- Pro: Can be pre-rendered. This makes the component appear immediately while page is loading.
- Con: Usually results in a more complex code, especially when a component looks very different on different screen sizes.
Usage:
import { media } from "../styles" function Thing() { return ( <div css={{ // Mobile-first: display: "block", // then enhance to desktop: [media(600)]: { display: "inline-block" }, }} > ... </div> ) }
-
React-based. Different markup is rendered based on window size.
- Pro: Can use different markup for different screen sizes. This usually results in simpler code.
- Con: Cannot be pre-rendered, because the server cannot send different HTML code based on screen size. Our
<Responsive />
component will only be mounted after JavaScript is loaded.
Usage:
import { Responsive } from "../styles" function Thing() { return ( <Responsive breakpoint={600} narrow={<ComponentForMobile />} wide={<ComponentForDesktop />} /> ) }
Note that
<Responsive />
will not be pre-rendered. This means that sometimes you might need to use both approaches together to prevent layout jumping. For example, if a component is 50 pixels high on mobile and 100 pixels high on desktop:import { Responsive } from "../styles" function Thing() { const breakpoint = 600 return ( <div css={{ // Reserve the space for component to be mounted. height: 50, [media(breakpoint)]: { height: 100 }, }} > <Responsive breakpoint={breakpoint} narrow={<ComponentForMobile />} wide={<ComponentForDesktop />} /> </div> ) }
Using JSDoc allows us to specify types of component props more expressively, and allows enhanced integration (and refactoring capability) with Visual Studio Code.
You can define all props in one line:
/**
* @param {{ party: IParty, hidden: boolean }} props
*/
export default function Unimplemented(props) {
...or you can also spell out each prop as a @param
(where some props needs extra elaboration):
/**
* @param {object} props
* @param {IParty} props.party
* @param {number} props.changeRate - The rate of change in score
*/
export default function MyComponent(props) {
yarn build
To release a new version, run /updateelectliveversion 1.0.0-beta.5
slash command in our development Slack channel (only available to collaborators). This will update package.json
file and deploy to the live website, and will also cause an update bar to display on user’s screen, asking them to refresh.