import { useCallback, useState } from 'react'; import { getOperationName } from '@apollo/client/utilities'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil'; import { IconButton } from '@/ui/button/components/IconButton'; import { IconPlus } from '@/ui/icon'; import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer'; import { GET_VIEW_FIELDS } from '@/views/queries/select'; import { useCreateViewFieldMutation, useUpdateViewFieldMutation, } from '~/generated/graphql'; import { toViewFieldInput } from '../hooks/useLoadView'; import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState'; import { addableViewFieldDefinitionsState, columnWidthByViewFieldIdState, viewFieldsState, } from '../states/viewFieldsState'; import type { ViewFieldDefinition, ViewFieldMetadata, } from '../types/ViewField'; import { ColumnHead } from './ColumnHead'; import { EntityTableColumnMenu } from './EntityTableColumnMenu'; import { SelectAllCheckbox } from './SelectAllCheckbox'; const COLUMN_MIN_WIDTH = 75; const StyledColumnHeaderCell = styled.th<{ columnWidth: number; isResizing?: boolean; }>` ${({ columnWidth }) => ` min-width: ${columnWidth}px; width: ${columnWidth}px; `} position: relative; user-select: none; ${({ isResizing, theme }) => { if (isResizing) { return `&:after { background-color: ${theme.color.blue}; bottom: 0; content: ''; display: block; position: absolute; right: -1px; top: 0; width: 2px; }`; } }}; `; const StyledResizeHandler = styled.div` bottom: 0; cursor: col-resize; padding: 0 ${({ theme }) => theme.spacing(2)}; position: absolute; right: -9px; top: 0; width: 3px; z-index: 1; `; const StyledAddIconButtonWrapper = styled.div` display: inline-flex; position: relative; `; const StyledAddIconButton = styled(IconButton)` border-radius: 0; `; const StyledEntityTableColumnMenu = styled(EntityTableColumnMenu)` position: absolute; right: 0; top: 100%; z-index: ${({ theme }) => theme.lastLayerZIndex}; `; export function EntityTableHeader() { const theme = useTheme(); const [{ objectName, viewFields }, setViewFieldsState] = useRecoilState(viewFieldsState); const columnWidths = useRecoilValue(columnWidthByViewFieldIdState); const addableViewFieldDefinitions = useRecoilValue( addableViewFieldDefinitionsState, ); const [offset, setOffset] = useRecoilState(resizeFieldOffsetState); const [initialPointerPositionX, setInitialPointerPositionX] = useState< number | null >(null); const [resizedFieldId, setResizedFieldId] = useState(null); const [isColumnMenuOpen, setIsColumnMenuOpen] = useState(false); const [createViewFieldMutation] = useCreateViewFieldMutation(); const [updateViewFieldMutation] = useUpdateViewFieldMutation(); const handleResizeHandlerStart = useCallback((positionX: number) => { setInitialPointerPositionX(positionX); }, []); const handleResizeHandlerMove = useCallback( (positionX: number) => { if (!initialPointerPositionX) return; setOffset(positionX - initialPointerPositionX); }, [setOffset, initialPointerPositionX], ); const handleResizeHandlerEnd = useRecoilCallback( ({ snapshot, set }) => () => { if (!resizedFieldId) return; const nextWidth = Math.round( Math.max( columnWidths[resizedFieldId] + snapshot.getLoadable(resizeFieldOffsetState).valueOrThrow(), COLUMN_MIN_WIDTH, ), ); if (nextWidth !== columnWidths[resizedFieldId]) { // Optimistic update to avoid "bouncing width" visual effect on resize. setViewFieldsState((previousState) => ({ ...previousState, viewFields: previousState.viewFields.map((viewField) => viewField.id === resizedFieldId ? { ...viewField, columnSize: nextWidth } : viewField, ), })); updateViewFieldMutation({ variables: { data: { sizeInPx: nextWidth }, where: { id: resizedFieldId }, }, refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''], }); } set(resizeFieldOffsetState, 0); setInitialPointerPositionX(null); setResizedFieldId(null); }, [resizedFieldId, columnWidths, setResizedFieldId], ); useTrackPointer({ shouldTrackPointer: resizedFieldId !== null, onMouseDown: handleResizeHandlerStart, onMouseMove: handleResizeHandlerMove, onMouseUp: handleResizeHandlerEnd, }); const toggleColumnMenu = useCallback(() => { setIsColumnMenuOpen((previousValue) => !previousValue); }, []); const handleAddViewField = useCallback( (viewFieldDefinition: ViewFieldDefinition) => { setIsColumnMenuOpen(false); if (!objectName) return; createViewFieldMutation({ variables: { data: toViewFieldInput(objectName, { ...viewFieldDefinition, columnOrder: viewFields.length + 1, }), }, refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''], }); }, [createViewFieldMutation, objectName, viewFields.length], ); return ( {viewFields.map((viewField) => ( { setResizedFieldId(viewField.id); }} /> ))} {addableViewFieldDefinitions.length > 0 && ( } onClick={toggleColumnMenu} /> {isColumnMenuOpen && ( )} )} ); }