Skip to main content
Glama

Convex MCP server

Official
by get-convex
Table.tsx13.2 kB
import { GenericDocument } from "convex/server"; import classNames from "classnames"; import { MutableRefObject, useCallback, useContext, useReducer, useRef, useState, } from "react"; import { Column, useBlockLayout, useColumnOrder, useResizeColumns, useTable, } from "react-table"; import { FixedSizeList } from "react-window"; import { Filter, SchemaJson, } from "system-udfs/convex/_system/frontend/lib/filters"; import { DndProvider } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; import withScrolling from "react-dnd-scrolling"; import { ImperativePanelHandle, Panel, PanelGroup, } from "react-resizable-panels"; import { cn } from "@ui/cn"; import { ResizeHandle } from "@common/layouts/SidebarDetailLayout"; import { useTableDensity } from "@common/features/data/lib/useTableDensity"; import { InfiniteScrollList } from "@common/elements/InfiniteScrollList"; import { SelectionState } from "@common/features/data/lib/useSelectionState"; import { usePatchDocumentField } from "@common/features/data/components/Table/utils/usePatchDocumentField"; import { DataRow } from "@common/features/data/components/Table/DataRow"; import { TableScrollbar } from "@common/features/data/components/Table/TableScrollbar"; import { useTrackColumnWidths } from "@common/features/data/components/Table/utils/useTrackColumnWidths"; import type { PopupState } from "@common/features/data/lib/useToolPopup"; import { useMaintainScrollPositionOnChange } from "@common/features/data/components/Table/utils/useMaintainScrollPositionOnChange"; import { TableContextMenu, useTableContextMenuState, } from "@common/features/data/components/Table/TableContextMenu"; import { TableHeader } from "@common/features/data/components/Table/TableHeader"; import { useStoredColumnOrder } from "@common/features/data/components/Table/utils/useDataColumns"; import { ViewDocument } from "@common/features/data/components/Table/ViewDocument"; import { pageSize } from "@common/features/data/components/Table/utils/useQueryFilteredTable"; import { LoadingLogo } from "@ui/Loading"; import { DeploymentInfoContext } from "@common/lib/deploymentContext"; import { useNents } from "@common/lib/useNents"; const ScrollingComponent = withScrolling("div"); const getRowId = (d: GenericDocument) => d._id as string; const pageThreshold = 25; export function Table({ activeSchema, columns = [], data = [], localStorageKey = "_disabled_", areEditsAuthorized, onAuthorizeEdits, hasFilters, selectedRows, loadMore, totalRowCount, listRef, patchDocument, tableName, componentId, isProd, hasPopup, setPopup, deleteRows, onAddDraftFilter, defaultDocument, sort, }: { activeSchema: SchemaJson | null; areEditsAuthorized: boolean; onAuthorizeEdits?(): void; columns: Column<GenericDocument>[]; data: GenericDocument[]; // array of row data so far localStorageKey?: string; tableName: string; componentId: string | null; isProd: boolean; selectedRows: SelectionState; totalRowCount?: number; hasFilters: boolean; loadMore: () => void; listRef: MutableRefObject<FixedSizeList | null>; patchDocument: ReturnType<typeof usePatchDocumentField>; hasPopup: boolean; setPopup: PopupState["setPopup"]; deleteRows: (rowIds: Set<string>) => Promise<void>; onAddDraftFilter: (newFilter: Filter) => void; defaultDocument: GenericDocument; sort: { order: "asc" | "desc"; field: string; }; }) { const { useCurrentDeployment, useHasProjectAdminPermissions } = useContext( DeploymentInfoContext, ); const deployment = useCurrentDeployment(); const hasAdminPermissions = useHasProjectAdminPermissions( deployment?.projectId, ); const { selectedNent } = useNents(); const isInUnmountedComponent = !!( selectedNent && selectedNent.state !== "active" ); const canManageTable = deployment?.deploymentType !== "prod" || hasAdminPermissions; const [storedColumnOrder, setStoredColumnOrder] = useStoredColumnOrder(localStorageKey); const dataColumnNames = columns.map((c) => c.Header as string); const { state, getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, setColumnOrder, } = useTable( { columns, data, getRowId, autoResetSortBy: false, initialState: { columnOrder: storedColumnOrder || dataColumnNames, }, stateReducer: (newState, action) => { if (action.type === "setColumnOrder") { setStoredColumnOrder(newState.columnOrder); } return newState; }, }, useBlockLayout, useColumnOrder, useResizeColumns, ); trackDataColumnChanges(dataColumnNames, storedColumnOrder, setColumnOrder); const reorderColumns = useCallback( (item: { index: number }, newIndex: number) => { const { index: currentIndex } = item; const currentItem = state.columnOrder[currentIndex]; const newColumnOrder = [...state.columnOrder]; newColumnOrder.splice(currentIndex, 1); newColumnOrder.splice(newIndex, 0, currentItem); setColumnOrder(newColumnOrder); }, [setColumnOrder, state.columnOrder], ); const resetColumnWidths = useTrackColumnWidths(state, localStorageKey); const { isResizingColumn } = state.columnResizing; const [ , { has: isRowSelected, toggle: toggleIsRowSelected, toggleAll, all: allRowsSelected, isExhaustive: isSelectionExhaustive, }, ] = selectedRows; const outerRef = useRef<HTMLElement>(null); const [, forceRerender] = useReducer((x) => x + 1, 0); const [topBorderAnimation, setTopBorderAnimation] = useState(false); const animateTopBorder = useCallback(() => { setTopBorderAnimation(true); setTimeout(() => setTopBorderAnimation(false), 1000); }, []); const { densityValues } = useTableDensity(); useMaintainScrollPositionOnChange( data, outerRef, getRowId, densityValues.height, animateTopBorder, ); const { contextMenuState, openContextMenu, closeContextMenu } = useTableContextMenuState(); const [collapsed, setCollapsed] = useState(false); const panelRef = useRef<ImperativePanelHandle>(null); const onEditDocument = useCallback( (document: GenericDocument) => { setPopup({ type: "editDocument", document, tableName, }); }, [setPopup, tableName], ); return ( <PanelGroup direction="horizontal" className="w-full" autoSaveId="documentViewer" > <Panel defaultSize={100} className="relative w-full overflow-x-hidden rounded-lg" > <DndProvider backend={HTML5Backend}> <ScrollingComponent {...getTableProps()} className={classNames( "flex w-full h-full overflow-y-hidden", "scrollbar", )} > <div className="flex flex-auto flex-col"> <TableHeader key={state.columnOrder.join(",")} reorder={reorderColumns} headerGroups={headerGroups} isResizingColumn={isResizingColumn} allRowsSelected={allRowsSelected} hasFilters={hasFilters} isSelectionExhaustive={isSelectionExhaustive} toggleAll={toggleAll} topBorderAnimation={topBorderAnimation} openContextMenu={openContextMenu} sort={sort} activeSchema={activeSchema} tableName={tableName} /> {/* Body */} <div {...getTableBodyProps()} className="mt-[-1px] w-full flex-auto" id="dataTable" > <InfiniteScrollList className="scrollbar-none" items={rows} totalNumItems={totalRowCount} overscanCount={25} pageSize={pageSize} loadMoreThreshold={pageThreshold} itemSize={densityValues.height} itemData={{ areEditsAuthorized, isRowSelected, isSelectionAllNonExhaustive: !isSelectionExhaustive && allRowsSelected === true, onAuthorizeEdits, patchDocument, prepareRow, rows, tableName, toggleIsRowSelected, onOpenContextMenu: openContextMenu, onCloseContextMenu: closeContextMenu, contextMenuRow: contextMenuState?.selectedCell?.rowId ?? null, contextMenuColumn: contextMenuState?.selectedCell?.column ?? null, canManageTable: canManageTable && !isInUnmountedComponent, activeSchema, resizingColumn: isResizingColumn, onEditDocument, }} RowOrLoading={DataRow} loadMore={loadMore} listRef={listRef} outerRef={outerRef} onScroll={() => { // Force a re-render so the TableScrollbar gets updated. forceRerender(); }} itemKey={(idx) => (data[idx] as any)?._id || idx} /> </div> <TableScrollbar totalRowCount={totalRowCount} outerRef={outerRef} listRef={listRef} /> </div> <TableContextMenu data={data} state={contextMenuState} close={closeContextMenu} isProd={isProd} setPopup={setPopup} deleteRows={deleteRows} onAddDraftFilter={onAddDraftFilter} defaultDocument={defaultDocument} canManageTable={canManageTable} resetColumns={() => { setColumnOrder(dataColumnNames); setStoredColumnOrder(dataColumnNames); resetColumnWidths(); }} /> </ScrollingComponent> </DndProvider> </Panel> {!hasPopup && selectedRows[0].size > 0 && ( <> <ResizeHandle collapsed={collapsed} direction="left" panelRef={panelRef} handleTitle="View Selected" /> <Panel defaultSize={30} minSize={10} maxSize={80} collapsible collapsedSize={0} onCollapse={() => setCollapsed(true)} onExpand={() => setCollapsed(false)} ref={panelRef} className={cn( "max-h-full overflow-x-auto bg-background-primary", !collapsed && "min-w-[14rem]", )} > <ViewDocument rows={data.filter((d) => Array.from(selectedRows[0]).includes(d._id as string), )} columns={dataColumnNames} tableName={tableName} componentId={componentId} canManageTable={canManageTable} areEditsAuthorized={areEditsAuthorized} onAuthorizeEdits={onAuthorizeEdits} activeSchema={activeSchema} /> </Panel> </> )} </PanelGroup> ); } export function TableSkeleton() { return ( <div className="flex h-full items-center justify-center rounded-sm border bg-background-secondary"> <LoadingLogo /> </div> ); } // Checks if there are any new or removed columns in the data set // and updates the column order accordingly. function trackDataColumnChanges( columns: string[], storedColumnOrder: string[] | undefined, updateColumnOrder: (newOrder: string[]) => void, ) { if (!storedColumnOrder) { return; } // Find columns that are not in the stored order and add them to the end const newColumns = columns .filter((c) => !storedColumnOrder.includes(c)) // New columns should be sorted alphabetically after the existing sort order. .sort((a, b) => a.localeCompare(b)); const existingColumns = storedColumnOrder.filter((c) => columns.includes(c)); // There are no new columns and no columns have been removed, so we don't need to make an update. if ( newColumns.length === 0 && existingColumns.length === storedColumnOrder.length ) { return; } const lastColumn = existingColumns[existingColumns.length - 1]; // If _creationTime is the last column, we should respect that and // insert the new columns before it. const newOrder = lastColumn === "_creationTime" ? [ ...existingColumns.filter((c) => c !== "_creationTime"), ...newColumns, "_creationTime", ] : [...existingColumns, ...newColumns]; updateColumnOrder(newOrder); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server