The Web UI
Overview
A ReactJS based Web UI to speak to a MakeHaus Node JS application. The application is intended and tested to be served by the web server baked into makehaus-js. For development purposes, the application is served by the bootstrapping web server (more details on the development workflow given below).
Branching
The develop
branch is the default development branch.
Cloning And Initial Setup
Clone the repository and from the working directory execute
https://github.com/makeproaudio/glue-react-browser.git npm i
This will pull all necessary dependencies as defined in package.json
into the node_modules/
folder.
Repository Structure
A few files are produced by the bootstrapper. Only relevant files used in development are mentioned below
node_modules/ public/ index.html manifest.json worker.js src/ components/ layout/ mpa-custom-ui-widgets/ mpa-material-ui-widgets/ mpa-widget-interface/ style/ App.js index.js serviceWorker.js style-helper.js package.json
Typical Development Workflow
With create-react-app
, a default development server is provided so that any change you make in sources is immediately deployed and reflected in the development server. To run the development server, enter:
npm start
This should launch the application on port 3000 unless another application uses that port. The development server’s caching is not 100% reliable, therefore it is recommended to adopt the following measures in your development setup. Ensure to revert before making a new build:
- Work in incognito mode
- Disable the service worker - Refer to
public/index.html
for comments on how to do this. - The React toolchain does not have a direct breakpoint debugger interaction with VSCode. The recommendation is to add
console.log()
to trace application flow.
Deployment
Making A Minified Build
To create a new deployment build, ensure that you have reverted any steps you may have taken to ease your development workflow. Execute the following command:
npm run build
This will output a minified version of the web application into a new folder called build
in the root working directory folder. If the build
folder existed earlier, a fresh build will replace its contents.
Deploying To makehaus-js
Copy the contents of the build
folder and paste it into the webapp
folder of makehaus-js.
Technology
- ReactJS as the front end library
- FlexBox for css based layouting
- MaterialUI as the standard widget library
- socket.io for network communication over websockets. Socket IO allows topic based event driven communication.
- serviceworker.js for Progressive Web App capabilities
Notes and Caveats
- At the time of writing the application, react 16 was the latest version.
- create-react-app was used to bootstrap the react project.
- Widgets have been implemented using MaterialUI but are not limited to MaterialUI. It is perfectly valid to implement widgets with a different library.
- socket.io was chosen as the standard network communication library for its simplicity and performance.
- Some package dependencies inside package.json can be pruned as they are probably not being used at the moment.
- The dockerfile that you find inside the repository was a good to have feature and is not necessary in developing the application. It may not be in a valid state.
Architecture
The most important components in the application are:
- Connection To Server
- Layouter
- Widget Interface
- Widget Helper and Implementations
Connection To Server
To load the application, the Host URL must be known. This Host URL is determined by the web server baked into makehaus-js and the web app port you supply to your makehaus-js application at the time of initialization. By default, the web app port is 3000.
Once you navigate to the Host URL, you’ll be asked to enter the makehaus websocket port used for communicating to makehaus . By default, the websocket server port is 8001.
For more details on default ports and makehaus-js configuration, refer to the makehaus-js documentation.
After setting up your port correctly, your makehaus-js layout will be transferred to the web application.
The entry point of the web application is componentDidMount()
inside App.js
Refer to comments in App.js
for application flow.
Layouter
Once the initial handshake is over and layout data has been received by the web application, the Layouter takes over. It is the responsibility of the components in /src/components/layout
to layout the json received from the server. This is a simple html/css driven implementation.
Widget
The Web UI is primarily server driven and relies on events being transmitted from the server to change its own state. The “rx
” topic is a fast lane for communication specifically oriented towards widgets.
The heavy lifting of the code is done by the Widget component in /src/components/mpa-widget-interface/widget.jsx
. Refer to comments in this file for application flow.
For the web application, a Widget
is a network abstraction layer over the different types of widgets that MakeHaus supports. A Widget
can change its type, therefore the Widget
component tunes into TYPECHANGE
messages coming from the server on the “rx
” topic.
Depending on the widget metadata as received by the server, the Widget
component spawns instances of standard MPA Widget Interface components. These Widget Interface components are standardized and can be found in the src/components/mpa-widget-interface package
.
Example: Slider
Here is a detailed explanation for the Slider MPA Widget Interface component located at src/components/mpa-widget-interface/slider.jsx
.
The entry point is the componentDidMount()
function. Here, a handler is registered on the “rx
” topic to receive any necessary updates from the server. The different events that may come in from the server are:
TOUCH
- the corresponding hardware widget was touchedVALUE
- the value of the widget was updatedCOLOR
- a color change was triggered on the widgetCONTEXT
- the context label of the widget was updatedLABEL
- the main label of the widget was updatedTYPECHANGE
- the min/max/values of the widget was updated.
Note that the TYPECHANGE
here does not have the same meaning as the TYPECHANGE
for the base Widget
component.
Not every Widget may support each and every one of these events. Each MPA Widget Interface then chooses to do whatever it pleases with the event.
The render()
function is where the actual UI implementation of the Widget is rendered.
Widget Helper and Implementations
The ideology of this architecture is to keep the MPA Widget Interface decoupled from the UI Widget implementation. Currently, most of the UI Widget implementations make use of the Material UI library. However, in the future, if we wish to change from Material UI to another library, the MPA Widget interfaces provide the flexibility to do so without altering the core network communication between the web application and the server.
The UI Widget implementations can be found in the following packages:
src/components/mpa-custom-ui-widgets src/components/mpa-material-ui-widgets
Supporting A New Widget
Support a new Widget may mean one of two different things:
- Are you supporting a new UI implementation of an existing MPA Widget Interface
- Are you building a new MPA Widget Interface from scratch?
To create a new UI implementation of an existing MPA Widget Interface, check off the following items:
- Create a new component for the UI Widget. If you’re implementing a new Material UI based widget, place it under
src/components/mpa-material-ui-widgets
otherwise if it’s a custom widget, place it undersrc/components/mpa-custom-ui-widgets
. As a convention, if you’re implementing a new widget from an existing baseline UI library, create a new folder undersrc/components
. - Let’s assume you’d like to replace the existing UI Button with a different Button. In the
render()
function ofsrc/components/mpa-widget-interface/button.jsx
, replace thereturn
section to return your new component that you’ve just created. - For any property of the component that is rendered by a server change(eg. color, label, context, min, max, etc) make sure you reference the correct state property variable of the MPA Widget Interface defined in the constructor in the component that you return in the
render()
function.
To create a new MPA Widget Interface, check off the following items:
- Create a new component for the MPA Widget Interface and place it under
src/components/mpa-widget-interface
- In the
constructor()
function of the new MPA Widget Interface, destructure the props that you want to get from metadata.stack and pass these initial values to the state of the component. - In the
componentWillUnmount()
function of the new MPA Widget Interface, ensure to deregister to the “rx
” topic from the socket to avoid memory leaks. - In the
componentDidMount()
function of the new MPA Widget Interface, register a new handler on the “rx
” topic and implement the events you wish to listen to. The list of events is specified in the Slider example mentioned above. - In the
render()
function of the new MPA Widget Interface, return the UI Widget Implementation you wish to render. Refer to the section above for details on how to create a new UI Widget implementation. - Inside the
Widget
component undersrc/components/mpa-widget-interface/widget.jsx
, navigate to thewidgetForMeta
property and add a new condition which catches the name of the widget you send from the server. - In the return section of the new conditional clause, return the new MPA Widget Interface you created wrapped inside a div tag.
Reporting Widget Events To The Server
For certain widgets, you may wish to send event callbacks from the UI widget to the server. An example of this can be found in the handleClick()
function of the Button
component under src/components/mpa-widget-interface/button.jsx
.The topic used here is called “tx
” and socket.emit()
is the method to send data to the server. There is no specific structure to the data that you pass to the server on the “tx
” topic, so take sufficient caution to implement the data format appropriately on the server.
Progressive Web App Support
PWAs allow a web application to be run in a native sandbox inside supported mobile browsers without having to install an application via the native application store. Although PWAs are a huge box, the MakeHaus Web UI uses minimal features of PWAs. The relevant files to modify are public/manifest.json
, public/worker.js
and src/serviceWorker.js
. The setup required for making an application a PWA is a one time task and the only relevant changes to be made are in the manifest.json
file, which contains metadata regarding what the application name should be, the icon to be displayed, the splash screen, theming on the splash screen, orientation etc. All other files shouldn’t be touched unless a new PWA feature is needed to be implemented.
Outlook
- Network Discovery: To truly make the application mobile, create a react native wrapper which can do basic discovery of makehaus-js web servers in the network. This will save the user from having to navigate to the Host URL. The UI application can continue to be shown using a WebView widget.
- Layouter New Feature: Currently, only Rows are supported from the server. If Columns are also to be implemented, the Layouter will need to be modified to become a matrix.
- New Widgets: Label / Heading, Text Input Field
- Code Cleanup: Add a prettier config file to implement a standard code formatter
- Code Cleanup: Map all events from the server to a standardized enum
- Code Cleanup: Replace React Class Components with pure function components.
- Code Cleanup: Replace socket.io with useSocket hooks
- Code Cleanup: Create custom React hooks to implement events in MPA Widget Interface components. In that case, the componentDidMount() function can be split to more atomic functions such as onColor, onLabel, onContext etc.