export function MasterDetail()

in desktop/flipper-plugin/src/ui/MasterDetail.tsx [76:249]


export function MasterDetail<T extends object>({
  dataSource,
  records,
  sidebarComponent,
  sidebarPosition,
  sidebarSize,
  onSelect,
  extraActions,
  enableMenuEntries,
  enableClear,
  isPaused,
  selection,
  onClear,
  ...tableProps
}: MasterDetailProps<T> & DataTableProps<T>) {
  useAssertStableRef(isPaused, 'isPaused');
  useAssertStableRef(selection, 'selection');

  const pluginInstance = usePluginInstance();
  const {client} = pluginInstance;
  const connected = useValue(pluginInstance.client.connected);

  const selectionAtom =
    // if no selection atom is provided, the component is uncontrolled
    // and we maintain our own selection atom
    // eslint-disable-next-line
    selection ?? useState(() => createState<T | undefined>(undefined))[0];
  const selectedRecord = useValue(selectionAtom);

  // if a tableManagerRef is provided, we piggy back on that same ref
  // eslint-disable-next-line
  const tableManagerRef = tableProps.tableManagerRef ?? createRef<undefined | DataTableManager<T>>();

  const pausedState = useValue(isPaused, false);

  const sidebar =
    sidebarPosition !== 'none' && selectedRecord && sidebarComponent
      ? createElement(sidebarComponent, {
          record: selectedRecord,
        })
      : null;

  const handleSelect = useCallback(
    (record: T | undefined, records: T[]) => {
      selectionAtom.set(record);
      onSelect?.(record, records);
    },
    [selectionAtom, onSelect],
  );

  const handleTogglePause = useCallback(() => {
    isPaused?.set(!isPaused?.get());
  }, [isPaused]);

  const handleClear = useCallback(() => {
    handleSelect(undefined, []);
    if (dataSource) {
      dataSource.clear();
      onClear?.();
    } else {
      if (!onClear) {
        throw new Error(
          "onClear must be set when using 'enableClear' and 'records'",
        );
      }
      onClear();
    }
  }, [dataSource, onClear, handleSelect]);

  const handleCreatePaste = useCallback(() => {
    const selection = tableManagerRef.current?.getSelectedItems();
    switch (selection?.length) {
      case undefined:
      case 0:
        return;
      case 1:
        client.createPaste(JSON.stringify(selection[0], null, 2));
        break;
      default:
        client.createPaste(JSON.stringify(selection, null, 2));
    }
  }, [client, tableManagerRef]);

  const handleGoToBottom = useCallback(() => {
    const size = dataSource ? dataSource.view.size : records!.length;
    tableManagerRef?.current?.selectItem(size - 1);
  }, [dataSource, records, tableManagerRef]);

  useEffect(
    function setupMenuEntries() {
      if (enableMenuEntries) {
        if (enableClear) {
          client.addMenuEntry({
            action: 'clear',
            handler: handleClear,
          });
        }
        if (client.isFB) {
          client.addMenuEntry({
            action: 'createPaste',
            handler: handleCreatePaste,
          });
        }
        client.addMenuEntry({
          action: 'goToBottom',
          handler: handleGoToBottom,
        });
      }
    },
    [
      client,
      enableClear,
      enableMenuEntries,
      handleClear,
      handleCreatePaste,
      handleGoToBottom,
    ],
  );

  const table = (
    <DataTable<T>
      enableAutoScroll
      {...tableProps}
      dataSource={dataSource as any}
      records={records!}
      tableManagerRef={tableManagerRef}
      onSelect={handleSelect}
      extraActions={
        <>
          {connected && isPaused && (
            <Button
              title={`Click to ${pausedState ? 'resume' : 'pause'} the stream`}
              danger={pausedState}
              onClick={handleTogglePause}>
              {pausedState ? <PlayCircleOutlined /> : <PauseCircleOutlined />}
            </Button>
          )}
          {connected && enableClear && (
            <Button title="Clear records" onClick={handleClear}>
              <DeleteOutlined />
            </Button>
          )}
          {extraActions}
        </>
      }
    />
  );

  switch (sidebarPosition!) {
    case 'main':
      return (
        <Layout.Container grow>
          {table}
          <DetailSidebar>{sidebar}</DetailSidebar>
        </Layout.Container>
      );
    case 'right':
      return (
        <Layout.Right resizable width={sidebarSize}>
          {table}
          {sidebar}
        </Layout.Right>
      );
    case 'bottom':
      return (
        <Layout.Bottom resizable height={sidebarSize}>
          {table}
          {sidebar}
        </Layout.Bottom>
      );
    case 'none':
      return table;
  }
}