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), utilising the optional src
directory. More details can be found on the Next.js website, but a brief rundown is presented below.
src/
: Contains the application's source code, primarily in Typescript._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 src/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/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
.tsx
extension should be employed for files containing JSX, inclusive of React components. - The
.ts
extension 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
.css
files should be placed directly with their associated component.
- Type definitions in the
type
folder should usetype
instead ofinterface
.
- Use functional components instead of class components for React Components.
- Props for React components should use
interface
for 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
Before attempting local development with the platform, the below software needs to be installed and configured.
- Node.js & npm
MAPBOX_USERNAME
environment variableMAPBOX_API_KEY
environment 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.