This repository contains the Svelte app. Our tech stack is:
- Vite as the build tool
- Svelte as the UI framework
- MapLibre GL as the map
- GOV.UK frontend for styling
There are some related repositorites:
- atip-data-prep is a set of scripts to generate data files that the Scheme Browser loads
- route-snapper is a tool to draw routes and areas snapped to existing roads
We have only included instructions or guidance for environments we've worked on: if it doesn't work for you or your environment, please make a pull request so others don't face the same pain you did!
These are some resources to learn languages and libraries used in this repo. Feel free to add more!
- TypeScript
- Svelte
- MapLibre
- Examples
- Join the
#maplibre
channel on osmus Slack
- Rust
- Docs for different learning styles
- The Rust Book
- GOV.UK frontend
- Typography for CSS class reference
- Components
To run locally you'll need npm.
npm install
to install dependenciesnpm run setup-govuk
to rerun Sass and generate GOV.UK stylesnpm run dev
to run locally (N.B. you need to run all the above commands before running this command)- To mimic GCP deployment and see private layers locally, follow instructions in
.env
and runVITE_RESOURCE_BASE="" VITE_MIMIC_GCP_LOCALLY="true" npm run dev
- To mimic GCP deployment and see private layers locally, follow instructions in
npm run fmt
to auto-format codenpm run check
to see TypeScript errors
Upon first setup, you'll need to npm run setup-govuk
to get all mandatory
files for running.
If you're using Firefox locally to develop and get "import declarations may
only appear at top level" errors, upgrade to at least Firefox 112, go to
about:config
and enable dom.workers.modules.enabled
To run via Docker in VSCode you'll need:
- Docker
- VSCode with the extension "Dev Containers" installed
Once you've installed these you can:
- Open VSCode
- Press F1 to open input command at top of window
- Run
Dev Containers: Open Folder in Container
, and select theatip
folder - Press the plus on the top right of the terminal subwindow at the bottom of VSCode (by default) to open a terminal in your docker container
- Run
npm install
,npm run dev
, and then you should have the web app running on your docker image. VS Code should prompt you with a link to open it in your browser.
If you need to temporarily modify an NPM dependency (like say route-snapper
), you can:
- Manually modify something in
node_modules/
rm -rf node_modules/.vite; npm run dev
- The change should take effect. If not, Ctrl+Shift+R in browser to force a refresh
To clean up afterwards, delete node_modules
and npm i
again.
We use Playwright for end-to-end tests that mimic a user's actions.
- To run all tests:
npm run test
- To develop a test interactively,
npm run test -- --debug -g 'test name'
. You can omit the-g
to run all tests, or pass a test file instead. You can step through a test line-by-line with a browser open. Press "record" and then interact with the page normally, generating the equivalent test code. - Use
npm run test -- --ui
for another useful debugger
We use Github actions to run Playwright tests for every PR, and to deploy the site to Github pages. The deployent is slightly unusual -- instead of just building and publishing main
, it builds every single branch in the GH repo, unless the branch name starts with nobuild_
. This is used for easily sharing experiments for feedback. If you push a branch called xyz
, after the actions finish, https://acteng.github.io/atip/xyz should work.
Building each branch is best-effort; failed ones will be skipped. You can see which branches succeeded by checking https://github.com/acteng/atip/tree/gh-pages.
Hot module replacement means you can edit source code and have just that Svelte component live-reload in the browser, without needing to do anything. It's incredibly useful for rapid iteration, especially for simple things like styling. There are some gotchas to keep it working:
- Use source/layer components from
svelte-maplibre
instead of directly callingmap.addSource
andmap.addLayer
- Within a Svelte component or
.ts
, express event handlers using functions and remember to callmap.off()
withinonDestroy
. Refer to existing components. - When in doubt, Ctrl+Shift+R to hard refresh and get rid of any possible HMR weirdness
Simple state is isolated to a component when possible. Most of the app-wide state is setup in stores.js
.
- The
map
store has the MapLibre map. Components everywhere need to do something to it, so it's a global singleton - The
gjScheme
store is the source-of-truth GeoJSON for the current data. It's automatically synced to local storage.- Feature IDs are unique, numeric, and always start at 1. See
newFeatureId
. They are not stable over time; if you load a file or refresh the page and load from local storage, some of the IDs may adjust. - Each feature has 3 simple fields --
name
,description
, andintervention_type
(area
,route
,crossing
,other
). Anything produced by the route tool will also havelength_meters
,waypoints
. - Depending on the schema in use, the feature has other nested properties like
pipeline
, for other schemas. InterventionLayer
renders these "finalized" features.- When editing geometry of a feature, the
hide_while_editing
property is set.InterventionLayer
skips drawing these, so something else can draw the actively-being-modified state.
- Feature IDs are unique, numeric, and always start at 1. See
TODO: Visualize the component tree, and how props flow up and down, maybe with svelte-sight
Interactions on the map are split into distinct and exclusive modes. Toolbox.svelte
manages these.
Be very careful with reactive statements and modifying mode
directly. Instead, use changeMode
, which will call the previous mode's stop()
and the new's start()
. The modes share some underlying stateful (and not Svelte-friendly) objects (point_tool
, polygon_tool
, and route-snapper
), and managing these objects and listening to events can get tricky, especially in the middle of switching modes. See this article to understand Svelte reactive statements better.
TODO: Draw the finite state machine for modes