This directory contains the source code, configuration files, and build-time resources required to compile and package the TWA Visualisation Platform (TWA-ViP). The Platform represents the evolution of The World Avatar project's visualisation toolkit, improving the legacy TWA Visualisation Framework (TWA-VF). It aims to offer a quick & easy way to set up geospatial visualisations, analytical tools, and static online content.
The TWA Visualisation Platform takes the form of a Next.js project written using TypeScript, utilising both client and server-side codes. Next.js is a framework that sits atop the React.js library; on top of the component-based UI offering from React, Next.js adds support for server-side code, custom routing, and update data fetching routines.
This document is split into three key sections: Architecture, Style Guide and Local Development.
- TWA Visualisation Platform (TWA-ViP)
The code architecture adheres to industry-standard practices for Next.js and React. It is worth noting, though, that the TWA-ViP is a work in progress and not a perfect, static codebase. A brief breakdown is given below, but more detailed information can be seen within the code (and the code's comments) itself.
The project structure should match the recommended Next.js project structure (you can read about that here). More details can be found on the Next.js website, but a brief rundown is presented below.
- The application's source code is primarily in Typescript, organised into the following directories:
__tests__/: Jest based unit testsapp: The app router directory contains publicly discoverable pages and API routesio: Logic classes for input/output handlingmap: Map container and their related utility methodsstate: State management container based on Redux to ensure consistent application behaviourtypes: Custom data types used in the projectui: Custom UI componentsutils: Common utilities.eslintrc.js: Configuration for ESLintnext-env.d.ts: Exports Next.js types for the Typescript compilernext.config.js: Configuration module for Next.js projectspackage.json: Node project configuration file (also contains configuration for Jest)server.js: Starts a custom HTTP server that allows hosting of additional static-resource directoriestsconfig.json: Configuration for the Typescript compiler
CSS colors, font and icon sizes are standardised and available as variables in ui/css/globals.css. If you require additional colors or sizes, add them to the file. Having global css variables is preferable to accommodate dark and light themes while improving maintainence.
Following Next.js' current routing system (known as the AppRouter), rather than the older PagesRouter, the app/[lang]/page.tsx file acts as the entry point to the application when accessed by the user via a web browser. This reads the UI settings file on the server and then proceeds to load the Landing Page or, if disabled, the Map container.
The current design of the application presents a number of different pages, each with their own URL route. This includes pages such as the landing page, the visualisation page, and a number of optional additional pages (generated from user-provided Markdown files). It's worth noting, however, that this could also be accomplished by presenting the application as a single page, with these components swapping in and out. Whilst this would remove the ability to bookmark/provide a link for a particular page, it may be more efficient and should be investigated.
The LandingPage child component contains a landing page with static content pulled from an optional markdown file (which can be provided at runtime, e.g. in a Docker volume) along with links to StaticContentPage instances each also reading from optional markdown files.
Next.js offers standardised special files to adhere to their established component hierarchy. Notably, layout.tsx provides a common layout for both existing routes and directly nested routes. loading.tsx serves as a fallback UI displayed immediately when navigating the application in the event of longer rendering. The component hierarchy is established as Layout -> Loading -> Page. Within the layout component, the GlobalContainer child component acts as a wrapper for all pages within the application. It provides global functionality such as a custom right-click menu, navigation bar, and Redux store provider (see here).
The Next.js application is delineated into server and client components across the network boundary. The server hosts the application code, which remains inaccessible to the client unless the client sends a request for data access. On the other hand, the client refers to the browser on a user's device, which sends requests to a server and transforms the server's response into a user interface displayed in the browser. By default, Next.js uses Server components.
To indicate the use of client components, 'use client' should be placed at the top of the component file. It's important to note that while Server components can render Client components, any nested components under these indicated Client components are automatically considered Client components.
Following these conventions, all server components are situated in the app directory, whereas all client components reside in the ui directory. Helper methods for either type of component are available in the other directories. It's essential to maintain a distinction between the server component (namely at page.ts) and the rendered content on the page (consisting of client component).
React follows a unique workflow that results in a specific method being required to store and update a component's state. In effect, it means that you cannot store a component's state in class/global variables like you would in a regular Javascript class.
If you create a custom component as a class or function (in this example let's say we have a MyComponent class that extends from React.Component), create an instance and try to render it, it's not the instance of the class that actually gets rendered; React creates an Element from your component instance and uses that. This is why you cannot then call methods to change the variables within your component instance. This is explained far clearer in the top answer here as well as in the article here.
To manage a component's state, React provides a per-component state management hook (read about them here). However, the state is stored within the component instance; this means that if you show/hide components by not rendering them from a parent component, each time they reappear, they will have been reinitialised with a default state.
In addition to the above, creating a component that can interact with other components is quite tricky. Depending on how far apart they are in the UI tree, this could involve passing around a metric crap tonne of callback functions as component properties. To address this, Redux has been added to the project to allow for a global store of states, allowing one component to update a global state and another to listen for changes in it and update accordingly. At the time of writing, this is primarily used in the custom right-click (context) menu; an option to hide the Map controls ribbon is added once the map is shown.
Note that this may not be the optimal solution. Some further research is warranted once we've spent more time working with React.
Next.js uses the public directory by default to house resources such as images, videos, configuration files etc. Unfortunately, Next requires this directory and its contents to be present at build time. To provide a location in which deploying developers can add their context-specific images & configurations, the code/public directory aims to be the target for mounting Docker volumes. Do note that this directory will be mapped automatically within a Docker container to the corresponding location for server or client interactions, but requires manual modification for local development.
Next.js assumes by default that the app will be deployed at a top level domain or subdomain.
However, in cases where page paths are utilised (e.g., https://www.example.org/page/), as seen in stack deployment and reverse proxy scenarios, additional configuration is necessary for Next.js to operate.
This includes specifying the assetPrefix option in the next.config.js file, or rather by specifying ASSET_PREFIX as an environment variable.
without proper handling, deploying at a subpath will break static CDN like imports (css and js chunks), media resources (images and other contents of the public folder), and page routing.
These should all be handled correctly if ASSET_PREFIX is set - it will append to all image URLs, page routes, and serve static chunks at that path.
Important
Always use next/Image i.e. <Image> and not <img> so that the URLs can be properly sanitised.
This project have the following dependent services:
The Feature Info Agent serves to retrieve data from the Knowledge Graph and pass it back to the TWA ViP for further interactions when called. The platform can currently visualise the metadata and time series returned from the agent, whether in full or by parts. To make it queriable in parts, please ensure an IRI is passed for the subproperty so for eg "Subproperty" : {"iri" : "PLACEHOLDER FOR IRI"}. An optional stack parameter can also be passed if the subquery should be executed on another stack.
This guide outlines a set of standards and best practices to ensure consistency and maintainability in our codebase. While the codebase is still being developed and may not fully comply with the following standards, please follow the conventions stated here.
- The
.tsxextension should be employed for files containing JSX, inclusive of React components. - The
.tsextension should be employed for pure Typescript files for functions and constants.
For example, page.tsx contains a JSX page render, and json.ts contains type definition for JSON objects.
- The corresponding
.cssfiles should be placed directly with their associated component.
- Type definitions in the
typefolder should usetypeinstead ofinterface.
- Use functional components instead of class components for React Components.
- Props for React components should use
interfacefor type checking.
// Good
interface ButtonProps {
text: string;
onClick: () => void;
}
function Button(props: ButtonProps) {
return <button onClick={onClick}>{text}</button>;
}
// Avoid
type ButtonProps = {
text: string;
onClick: () => void;
};
class Button extends React.Component<ButtonProps> {
render() {
return <button onClick={this.props.onClick}>{this.props.text}</button>;
}
}Additionally, reusable components are provided to facilitate this. For navigation, AppLink is available at ui/navigation/link/link.tsx. For graphics, use the default <Image> element provided by next
All colours are declared as CSS custom properties in ui/css/globals.css with light and dark variants. The @theme block maps each variable to a Tailwind utility class — always prefer these Tailwind classes in .tsx files (e.g. bg-primary, text-muted-foreground). Use raw var(--…) only in .module.css or inline styles.
When a colour is defined as a pair, use the full pair together for colour usage: combine the related background and foreground variables or the related Tailwind classes unless a component has a specific reason not to. For example, use --primary with --primary-foreground, bg-primary with text-primary-foreground, and --status-open-bg with --status-open-text.
| Group | Description | Variable | Tailwind Class |
|---|---|---|---|
| Core Background & Text | Main page background colour. Use in pair with --foreground. |
--background |
bg-background |
| Core Background & Text | Main text colour. Use in pair with --background. |
--foreground |
text-foreground |
| Core Background & Text | Secondary background surfaces such as nav bars, sidebars, and cards. Pair with --muted-foreground for supporting text or with --foreground for primary text. |
--muted |
bg-muted |
| Core Background & Text | Lower-emphasis text colour for descriptions, hints, and other supporting copy. | --muted-foreground |
text-muted-foreground |
| Core Background & Text | Highlight background for interactive controls such as toggles and tabs. | --ring |
bg-ring |
| Core Background & Text | Semi-transparent inverse background used as a modal overlay backdrop. | --background-inverse-primary |
bg-inverse-primary |
| Actions & Interactions | Primary call-to-action buttons and key links. Use the foreground pair together with the background token. | --primary / --primary-foreground |
bg-primary, text-primary-foreground |
| Actions & Interactions | Secondary or supporting actions. Use the foreground pair together with the background token. | --secondary / --secondary-foreground |
bg-secondary, text-secondary-foreground |
| Actions & Interactions | Background for delete, cancel, or error-related actions. | --destructive |
bg-destructive |
| Borders & Focus States | Default border colour. | --border |
border-border |
| Borders & Focus States | Keyboard focus ring colour for interactive elements. In Tailwind, use with focus-visible; for example focus-visible:ring-focus focus-visible:ring-[2px], where ring-[2px] sets the ring width (border thickness) so the focus ring is visible. |
--focus |
ring-focus |
| Miscellaneous | Non-critical information surfaces such as help banners, inline notices, guidance callouts, and tooltips. Use the background, text, and border tokens together. | --info-background / --info-foreground / --info-border |
bg-info-background, text-info-foreground, border-info-border |
| Miscellaneous | Default colours used when a state is undefined, neutral, or not applicable. Always apply the background and foreground pair together for proper contrast. | --neutral-background/--neutral-foreground |
bg-neutral-background, text-neutral-foreground |
| Miscellaneous | Colours used to highlight warnings, caution states, or situations that may require user attention. Always apply the background and foreground pair together. | --warning-background/--warning-foreground |
bg-warning-background, text-warning-foreground |
| Miscellaneous | Colours used to represent errors, failures, or terminated states. Also used for error notifications and toast messages. Always apply the background and foreground pair together. | --error-background/--error-foreground |
bg-error-background, text-error-foreground |
| Miscellaneous | Colours used to indicate successful outcomes, completed actions, or active/available states. Also used for success notifications and toast messages. Always apply the background and foreground pair together. | --success-background/--success-foreground |
bg-success-background, text-success-foreground |
These live in globals.css but are consumed only in .module.css files. Migrate to Tailwind classes when touching these components.
| Variable | Used By |
|---|---|
--background-secondary, --background-tertiary |
Scrollbars, context menus, map panels, ribbons |
--background-inverse-primary |
Dropdown selectors |
--text-color-primary, --text-color-secondary |
Floating panels, info trees, dropdowns |
--text-color-links, --text-color-links-hover |
Link colours |
--border-primary, --border-secondary, --border-tertiary |
Context menus, info trees, floating panels |
Before attempting local development with the platform, the below software needs to be installed and configured.
- Node.js & npm
MAPBOX_USERNAMEenvironment variableMAPBOX_API_KEYenvironment variable
Note that the environment variables listed above are only needed during local development. In production, Docker secrets within The Stack will handle these.
In addition to the above software requirements, it is also recommended that developers bring themselves up to date with the basics of the core technologies being utilised. The most critical for a basic understanding of the code are listed below.
- Typescript
- React
- Next.js
To install on the host machine, navigate to the code directory and run the npm install command. This will read the package.json file and install all required modules within a new node_modules directory (that should not be committed); this process may take several minutes.
In order to ensure that the code runs as expected, all configuration files must be placed at <root>\code\public\.
Once installed and the required configuration files are provided, the project can be run in development mode by using the npm run dev command, again from within the code directory.
On some Windows machines, this may cause the below error to appear; if this does happen, run the npm install -g win-node-env command to address it.
"NODE_ENV" is not recognized as an internal or external command, operable command or batch file.In development mode, the code also adds watches to source code files, automatically triggering the server to refresh/rerender pages when changes are made (known as "hot-loading").
Once running, the front page of the application should be available at http://localhost:3000.
Note
At the time of writing, a method allowing deploying developers to add their own custom code to the pre-generated TWA-ViP image has not been identified. This is something that needs further investigation.