matrix-org/matrix-react-sdk-module-api

Provide extension point to add and remove toolbar buttons

Fox32 opened this issue · 5 comments

Fox32 commented

Is your feature request related to a problem? Please describe.

We currently forked Element to provide the functionality described in element-hq/element-web#15737
We replace the widget toggle button from the toolbar with custom buttons for each widget. We heavily rely on widgets in our setup and want to make them more easily accessible.

Describe the solution you'd like

A general extension point that allows to remove certain toolbar buttons (e.g. at least the toggle widget button) and to add custom buttons (with title and icon) to it. We need to be able to dynamically change the buttons we add (not just once).

Describe alternatives you've considered

Alternative is to land an improvement into Element Web directly (see element-hq/element-web#15737). The issue is already open for some time, so I assume that there is currently no interest in landing such a change in Element itself. But we would also be glad to contribute it directly.

Additional context
I can provide additional details, e.g. the exact changes in out fork privately if you are interested.

Fox32 commented

Probably a separate issue, but this would also need access to the WidgetLayoutStore (or parts of it) to perform the operation itself.

I want to propose the following API surface to solve this issue. This is an example module using the described APIs:

An Element room header where the generic Apps icon is replaced with a button for each installed widget

Problem 1: Change the buttons in the room header

Create a new RoomHeaderLifecycle.Create lifecycle that can manipulate the startButtons and endButtons lists before rendering them.

We can use it in the matrix-react-sdk as follows:

// ...

export default class RoomHeader extends React.Component<IProps, IState> {
  // ...

  private renderButtons(isVideoRoom: boolean): React.ReactNode {
    const startButtons: JSX.Element[] = [];
    const endButtons: JSX.Element[] = [];

    // existing code that creates all the buttons for the toolbar

    // NEW: pass the configured buttons to the modules and let them edit the lists
    const opts: RoomHeaderCreateOpts = { startButtons, endButtons };
    ModuleRunner.instance.invoke(RoomHeaderLifecycle.Create, opts, this.props.room.roomId);

    // NEW: use `opts.startButtons`/`opts.endButtons` instead of `startButtons`/`endButtons`
    return (
      <>
        {opts.startButtons}
        <RoomHeaderButtons
            room={this.props.room}
            excludedRightPanelPhaseButtons={this.props.excludedRightPanelPhaseButtons}
        />
        {opts.endButtons}
      </>
    );
  }

  // ...
}

(based on https://github.com/matrix-org/matrix-react-sdk/blob/0ce3664434a885ca470fcaa9918f0fec69847d8f/src/components/views/rooms/RoomHeader.tsx#L654-L659)

A module can implement the lifecycle as follows:

export default class ExampleModule extends RuntimeModule {
  public constructor(moduleApi: ModuleApi) {
    super(moduleApi);

    this.on(RoomHeaderLifecycle.Create, this.onRoomHeaderCreate);
  }

  protected onRoomHeaderCreate: RoomHeaderCreateListener = (opts, roomId) => {
    // is called whenever the RoomHeader component is rendered

    opts.startButtons = opts.startButtons
      // Remove a button based on it's React key
      .filter((buttons) => buttons.key !== "apps")
      // add a new button to the list
      .concat(<MyNewButton key="new-button" />);
  };
}

Potential problems

Some issues we can discuss and either accept or change somehow:

  • Hooking into an implementation detail (-> startButtons, endButtons) adds friction when changes in the original <RoomHeader /> are needed in the future because the module api needs to be changed too.
  • The React key property is not guaranteed to be stable so a module could break after an update.

Additions

  • The Module API should provide a TooltipButton component so modules can add new buttons that fit into the design.

Problem 2: Access and manipulate the widget layout in the rooms

Problem 1 lets us add new buttons to the header, but we want to show active widgets on it and let users control which widgets are displayed to the users. So we still lack the interfaces to do that. We add it with two changes:

  1. Extend the ModuleApi with two endpoints to receive a list of all widgets and to change them in the layout:

    /**
     * Gets a list of widgets that are installed into the provided room as well as the container the
     * widgets are hosted.
     *
     * @param roomId The ID of the room to query.
     * @returns The widgets in the room.
     */
    getRoomWidgets(roomId: string): { allWidgets: IWidget[]; top: string[]; center: string[]; };
    
    /**
     * Move the room widget into another display container.
     *
     * @param roomId The ID of the room to query.
     * @param widgetId The ID of the widget to move to a different display container.
     * @param toContainer The container the widget should be moved to.
     */
    moveWidgetToContainer(roomId: string, widgetId: string, toContainer: WidgetContainer): void;
  2. Have a WidgetLifecycle.WidgetsChanged lifecycle that notifies the module that there were changes in the widgets and getRoomWidgets should be called again.

  3. Provide a WidgetAvatar component so modules can render the avatar of a widget.


Would this work or are there concerns with this approach?

@Fox32 @dhenneke I was discussing this with @nadonomy. We need to identify areas of the app (e.g. Timeline, right side of room header bar) that can be customized without breaking everything else. Then clients can build whatever solution(s) they want or need, without having to worry about their forked version messing with our regular updates.

We shall discuss internally and get back to you with more detail :)

@americanrefugee @germain-gg did you had any chance to discuss it internally, yet? We would be happy to create a PR once we are aligned how we want to proceed here.

@americanrefugee @germain-gg did you had any chance to discuss it internally, yet? We would be happy to create a PR once we are aligned how we want to proceed here.

We are still discussing internally what else can / should be customizable beyond the room header. @germain-gg or I shall update you with any more info as soon as we can!