open-rmf / rmf-web

Apache License 2.0
87 stars 41 forks source link

Dashboard: microapp v2 #999

Closed koonpeng closed 2 months ago

koonpeng commented 3 months ago

These are changes to the "microapp" architecture to make it more modular. The goal is to eventually combine react-components and the current dashboard and make it into a framework from which new dashboards can be easily created.

Configurable Microapps

Previously microapp is a "one and done" thing where there is no way to customize them. Now microapps can have customization options via a factory method.

foo.tsx

export interface FooProps {
  greeting: string
}

export default function Foo({ greeting }: FooProps) {
  return <div>{greeting}</div>
}

foo-app.ts

export default function createFooApp(config: FooProps) {
  return createMicroApp('foo', 'Foo', () => import('./foo'), (settings) => config);
}

The fourth argument to createMicroApp is a callback that returns the props that will be passed to the component when rendering. The map app is one example where the pattern is used.

MicroApp with Settings

On top of configurable micro apps, they can now also store runtime settings, in the props callback in createMicroApp, it receives a settings object, which has a microAppSettings: { [k: string]: unknown } field. Micro apps can store their settings there under their appId.

Lazy loaded Microapps

All microapps are now lazy loaded, notice above that when creating the foo app, the Foo component is loaded via a dynamic import. This allows vite to code split the micro apps and load only the microapps that are currently being used in a workspace.

Currently this results in reduction of the largest bundle from ~3mb to ~1.5mb.

Deferred Contexts

Normally contexts must always have a default value, in some cases, we may have a context that must always be available but it is not possible to set a default value. Previously the solution is to use null as the default value but through the blessing and curse of typescript, it means that we must have a non null assertion everywhere we use it. RmfIngress is one such example where it creates a lot of boilerplate.

Deferred contexts is a convenient wrapper that does the assertions. It will throw an error if no value is provided to the context yet.

export const [useFoo, FooProvider] = createDeferredContext<Foo>();

function MyComponent() {
  const foo = useFoo();
  foo.doStuff(); // no need for null assertion
  ...
}

function App() {
  const foo = new Foo();
  return <FooProvider value={foo}><MyComponent /></FooProvider>;
}

Easy dashboard customization

With the new microapps architecture, a new dashboard can be easily created with minimal code. Below is an example to create a minimal dashboard with a map, doors, lifts, robots and mutex groups app.

const mapApp = createMapApp({
  attributionPrefix: 'Open-RMF',
  defaultMapLevel: 'L1',
  defaultRobotZoom: 20,
  defaultZoom: 6,
});

const homeWorkspace: InitialWindow[] = [
  {
    layout: { x: 0, y: 0, w: 7, h: 4 },
    microApp: robotsApp,
  },
  { layout: { x: 8, y: 0, w: 5, h: 8 }, microApp: mapApp },
  { layout: { x: 0, y: 0, w: 7, h: 4 }, microApp: doorsApp },
  { layout: { x: 0, y: 0, w: 7, h: 4 }, microApp: liftsApp },
  { layout: { x: 8, y: 0, w: 5, h: 4 }, microApp: robotMutexGroupsApp },
];

export default function App() {
  return (
    <RmfDashboard
      apiServerUrl="http://localhost:8000"
      trajectoryServerUrl="http://localhost:8006"
      authenticator={new StubAuthenticator()}
      helpLink="https://osrf.github.io/ros2multirobotbook/rmf-core.html"
      reportIssueLink="https://github.com/open-rmf/rmf-web/issues"
      resources={{ fleets: {}, logos: { header: '/resources/defaultLogo.png' }}}
      tasks={{
        allowedTasks: [],
        pickupZones: [],
        cartIds: [],
      }}
      tabs={[
        {
          name: 'Home',
          route: '',
          element: <Workspace initialWindows={homeWorkspace} />,
        },
      ]}
    />
  );
}

Theming

Previously, themes are fixed, the only way to change the theme is to change the dashboard itself. With the move to a dashboard framework architecture, it is now possible to create dashboards with different themes. The examples/custom-theme shows an example of a dashboard with custom theme.

aaronchongth commented 2 months ago

To addon, I've found that pages (tabs) are no longer contained in the viewport, and users are required to scroll, to view the lift table under System Overview as well

koonpeng commented 2 months ago

To addon, I've found that pages (tabs) are no longer contained in the viewport, and users are required to scroll, to view the lift table under System Overview as well

Yeah, we no longer force the height of the workspace. But it is still possible to put all the micro apps without scrolling if you change the layout.

koonpeng commented 2 months ago

Thanks for the herculean effort! I've not looked at the code in detail yet, but started running some of my usual tests of the dashboard, and wanted to highlight some issues and questions first,

  • dashboard crashes when a task is completed (more specifically an alert is supposed to pop up) image

Thanks for helping to test, should be fixed now

aaronchongth commented 2 months ago

LGTM so far! With these changes I think we'll need to start documenting how the dashboard app can be configured overall, could you also help add that in?

koonpeng commented 2 months ago

There are some docs added in https://github.com/open-rmf/rmf-web/pull/1007. https://github.com/open-rmf/rmf-web/tree/koonpeng/merge-react-components/packages/rmf-dashboard-framework/docs, haven't really fully tested them in practice, would appreciate if you can help beta test it!