renzholy/mongood

View on GitHub
src/components/documents-list.tsx

Summary

Maintainability
B
5 hrs
Test Coverage
import { useState, useCallback, useMemo, useRef, useEffect } from 'react'
import { useAppSelector } from 'hooks/use-app'
import { Selection, IColumn, ColumnActionsMode } from '@fluentui/react'
import { get } from 'lodash-es'
import { runCommand } from 'utils/fetcher'
import { stringify } from 'utils/ejson'
import { MongoData, DisplayMode } from 'types'
import { useCommandFind, useCommandFindById } from 'hooks/use-command'
import usePromise from 'hooks/use-promise'
import { calcHeaders, mapToColumn } from 'utils/table'
import useRouterQuery from 'hooks/use-router-query'
import { useConnection } from 'hooks/use-connections'
import Table from './pure/table'
import EditorModal from './pure/editor-modal'
import DocumentRowContextualMenu from './document-row-contextual-menu'
import DocumentColumnContextualMenu from './document-column-contextual-menu'
import PromiseButton from './pure/promise-button'
import LargeMessage from './pure/large-message'
import MongoDataColorized from './pure/mongo-data-colorized'
import DocumentCell from './pure/document-cell'

type Document = { [key: string]: MongoData }

export default function DocumentsList() {
  const displayMode = useAppSelector((state) => state.docs.displayMode)
  const [{ conn, database, collection }] = useRouterQuery()
  const connection = useConnection(conn)
  const index = useAppSelector((state) => state.docs.index)
  const { data, error, mutate } = useCommandFind()
  const [isUpdateOpen, setIsUpdateOpen] = useState(false)
  const [isRowMenuHidden, setIsRowMenuHidden] = useState(true)
  const [isColumnMenuHidden, setIsColumnMenuHidden] = useState(true)
  const [invokedItem, setInvokedItem] = useState<Document>()
  const [editedItem, setEditedItem] = useState<Document>()
  const [column, setColumn] = useState<IColumn>()
  const handleUpdate = useCallback(
    async () =>
      database && collection
        ? runCommand(connection, database, {
            findAndModify: collection,
            query: { _id: (invokedItem as { _id: unknown })._id },
            update: editedItem,
          })
        : undefined,
    [connection, database, collection, invokedItem, editedItem],
  )
  const promiseUpdate = usePromise(handleUpdate)
  useEffect(() => {
    if (promiseUpdate.resolved) {
      mutate()
      setIsUpdateOpen(false)
    }
  }, [promiseUpdate.resolved, mutate])
  const rowTarget = useRef<MouseEvent>()
  const columnTarget = useRef<MouseEvent>()
  const selection = useMemo(() => new Selection<Document>(), [])
  const title = useMemo(() => stringify(invokedItem?._id), [invokedItem])
  const onItemInvoked = useCallback((item: Document) => {
    setInvokedItem(item)
    setIsUpdateOpen(true)
  }, [])
  const { data: invoked } = useCommandFindById(invokedItem?._id)
  useEffect(() => {
    setEditedItem(invoked?.cursor.firstBatch[0])
  }, [invoked?.cursor.firstBatch])
  const onItemContextMenu = useCallback(
    (ev: MouseEvent) => {
      if (selection.getSelectedCount() === 1) {
        const [item] = selection.getSelection()
        setInvokedItem(item)
      } else {
        setInvokedItem(undefined)
      }
      rowTarget.current = ev
      setIsRowMenuHidden(false)
    },
    [selection],
  )
  const order = useMemo(
    () => [
      '_id',
      ...Object.keys(index?.key || {}).map((key) => key.split('.')[0]),
      ...Object.keys(index?.weights || {}).map((key) => key.split('.')[0]),
    ],
    [index],
  )
  const index2dsphere = useMemo(
    () =>
      index?.['2dsphereIndexVersion']
        ? Object.entries(index.key).find(
            ([_key, value]) => value === '2dsphere',
          )?.[0]
        : undefined,
    [index],
  )
  const handleRenderItemColumn = useCallback(
    (item?: Document, _index?: number, _column?: IColumn) =>
      displayMode === DisplayMode.DOCUMENT ? (
        <MongoDataColorized value={item} />
      ) : (
        <DocumentCell
          value={item?.[_column?.key as keyof typeof item]}
          subStringLength={
            _column?.currentWidth ? undefined : _column?.minWidth! >> 2
          }
          index2dsphere={
            index2dsphere &&
            _column?.key &&
            index2dsphere.startsWith(_column?.key)
              ? get(item, index2dsphere)
              : undefined
          }
        />
      ),
    [index2dsphere, displayMode],
  )
  useEffect(() => {
    selection.setAllSelected(false)
  }, [selection, data])
  const columns = useMemo<IColumn[]>(() => {
    if (!data || data.cursor.firstBatch.length === 0) {
      return []
    }
    return displayMode === DisplayMode.TABLE
      ? mapToColumn(calcHeaders(data.cursor.firstBatch, order)).map((c) => ({
          ...c,
          columnActionsMode:
            c.key === '_id'
              ? ColumnActionsMode.disabled
              : ColumnActionsMode.clickable,
          onColumnContextMenu(_column, ev) {
            if (_column?.key === '_id') {
              return
            }
            columnTarget.current = ev?.nativeEvent
            setColumn(_column)
            setIsColumnMenuHidden(false)
          },
          onColumnClick(ev, _column) {
            columnTarget.current = ev.nativeEvent
            setColumn(_column)
            setIsColumnMenuHidden(false)
          },
        }))
      : [
          {
            key: '',
            name: 'Documents',
            minWidth: 0,
            isMultiline: true,
            columnActionsMode: ColumnActionsMode.disabled,
          },
        ]
  }, [displayMode, data, order])
  const handleGetKey = useCallback(
    (item: Document, i?: number) =>
      item._id ? JSON.stringify(item._id) : JSON.stringify(item) + i,
    [],
  )

  if (error) {
    return (
      <LargeMessage iconName="Error" title="Error" content={error.message} />
    )
  }
  if (!data) {
    return <LargeMessage iconName="HourGlass" title="Loading" />
  }
  return (
    <>
      <EditorModal<Document>
        title={title ? `View Document: ${title}` : 'View Document'}
        value={editedItem}
        onChange={setEditedItem}
        isOpen={isUpdateOpen}
        onDismiss={() => {
          setIsUpdateOpen(false)
        }}
        onDismissed={() => {
          setEditedItem(invokedItem)
        }}
        footer={(disabled) => (
          <PromiseButton
            text="Update"
            primary={true}
            disabled={disabled}
            promise={promiseUpdate}
            style={{ flexShrink: 0 }}
          />
        )}
      />
      <DocumentRowContextualMenu
        hidden={isRowMenuHidden}
        onDismiss={() => {
          setIsRowMenuHidden(true)
        }}
        target={rowTarget.current}
        selectedItems={selection.getSelection()}
        onEdit={
          invokedItem
            ? () => {
                setIsRowMenuHidden(true)
                setIsUpdateOpen(true)
              }
            : undefined
        }
      />
      <DocumentColumnContextualMenu
        value={column}
        hidden={isColumnMenuHidden}
        onDismiss={() => {
          setIsColumnMenuHidden(true)
        }}
        target={columnTarget.current}
      />
      <Table
        items={data.cursor.firstBatch}
        columns={columns}
        getKey={handleGetKey}
        onItemInvoked={onItemInvoked}
        onItemContextMenu={onItemContextMenu}
        selection={selection}
        onRenderItemColumn={handleRenderItemColumn}
      />
    </>
  )
}