main

mattermost/focalboard

Last updated at: 28/12/2023 01:42

tableHeaders.tsx

TLDR

This file is the component responsible for rendering the headers of a table in the Demo Projects project. It displays the column headers for the table and provides functionality for sorting, resizing, and rearranging the columns.

Methods

There are no methods in this file.

Classes

There are no classes in this file.

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useMemo} from 'react'

import {FormattedMessage, useIntl} from 'react-intl'

import {IPropertyTemplate, Board} from '../../blocks/board'
import {createBoardView, BoardView, ISortOption} from '../../blocks/boardView'
import {Card} from '../../blocks/card'
import {Constants} from '../../constants'
import mutator from '../../mutator'
import {Utils} from '../../utils'
import propsRegistry from '../../properties'

import './table.scss'

import TableHeader from './tableHeader'
import {useColumnResize} from './tableColumnResizeContext'

type Props = {
    board: Board
    cards: Card[]
    activeView: BoardView
    views: BoardView[]
    readonly: boolean
}

const TableHeaders = (props: Props): JSX.Element => {
    const {board, cards, activeView, views} = props
    const intl = useIntl()
    const columnResize = useColumnResize()

    const onAutoSizeColumn = useCallback((columnID: string, headerWidth: number) => {
        let longestSize = headerWidth
        const visibleProperties = board.cardProperties.filter(() => activeView.fields.visiblePropertyIds.includes(columnID)) || []
        const columnRef = columnResize.cellRef(columnID)
        if (!columnRef) {
            return
        }

        let template: IPropertyTemplate | undefined
        const columnFontPadding = Utils.getFontAndPaddingFromCell(columnRef)
        let perItemPadding = 0
        if (columnID !== Constants.titleColumnId) {
            template = visibleProperties.find((t: IPropertyTemplate) => t.id === columnID)
            if (!template) {
                return
            }
            if (template.type === 'multiSelect') {
                // For multiselect, the padding calculated above depends on the number selected when calculating the padding.
                // Need to calculate it manually here.
                // DOM Object hierarchy should be {cell -> property -> [value1, value2, etc]}
                let valueCount = 0
                if (columnRef.childElementCount > 0) {
                    const propertyElement = columnRef.children.item(0) as Element
                    if (propertyElement) {
                        valueCount = propertyElement.childElementCount
                        if (valueCount > 0) {
                            const statusPadding = Utils.getFontAndPaddingFromChildren(propertyElement.children, 0)
                            perItemPadding = statusPadding.padding / valueCount
                        }
                    }
                }

                // remove the "value" portion of the original calculation
                columnFontPadding.padding -= (perItemPadding * valueCount)
            }
        }

        cards.forEach((card) => {
            let thisLen = 0
            if (columnID === Constants.titleColumnId) {
                thisLen = Utils.getTextWidth(card.title, columnFontPadding.fontDescriptor) + columnFontPadding.padding
            } else if (template) {
                const property = propsRegistry.get(template.type)
                property.valueLength(card.fields.properties[columnID], card, template as IPropertyTemplate, intl, columnFontPadding.fontDescriptor, perItemPadding)
                thisLen += columnFontPadding.padding
            }
            if (thisLen > longestSize) {
                longestSize = thisLen
            }
        })

        const columnWidths = {...activeView.fields.columnWidths}
        columnWidths[columnID] = longestSize
        const newView = createBoardView(activeView)
        newView.fields.columnWidths = columnWidths
        mutator.updateBlock(board.id, newView, activeView, 'autosize column')
    }, [activeView, board, cards])

    const visiblePropertyTemplates = useMemo(() => (
        activeView.fields.visiblePropertyIds.map((id) => board.cardProperties.find((t) => t.id === id)).filter((i) => i) as IPropertyTemplate[]
    ), [board.cardProperties, activeView.fields.visiblePropertyIds])

    const onDropToColumn = useCallback(async (template: IPropertyTemplate, container: IPropertyTemplate) => {
        Utils.log(`ondrop. Source column: ${template.name}, dest column: ${container.name}`)

        // Move template to new index
        const destIndex = container ? activeView.fields.visiblePropertyIds.indexOf(container.id) : 0
        await mutator.changeViewVisiblePropertiesOrder(board.id, activeView, template, destIndex >= 0 ? destIndex : 0)
    }, [board.id, activeView.fields.visiblePropertyIds])

    const titleSortOption = activeView.fields.sortOptions?.find((o) => o.propertyId === Constants.titleColumnId)
    let titleSorted: 'up' | 'down' | 'none' = 'none'
    if (titleSortOption) {
        titleSorted = titleSortOption.reversed ? 'down' : 'up'
    }

    return (
        <div
            className='octo-table-header TableHeaders'
            id='mainBoardHeader'
        >
            <TableHeader
                name={
                    <FormattedMessage
                        id='TableComponent.name'
                        defaultMessage='Name'
                    />
                }
                sorted={titleSorted}
                readonly={props.readonly}
                board={board}
                activeView={activeView}
                cards={cards}
                views={views}
                template={{id: Constants.titleColumnId, name: 'title', type: 'text', options: []}}
                onDrop={onDropToColumn}
                onAutoSizeColumn={onAutoSizeColumn}
            />

            {/* Table header row */}
            {visiblePropertyTemplates.map((template) => {
                let sorted: 'up' | 'down' | 'none' = 'none'
                const sortOption = activeView.fields.sortOptions.find((o: ISortOption) => o.propertyId === template.id)
                if (sortOption) {
                    sorted = sortOption.reversed ? 'down' : 'up'
                }
                return (
                    <TableHeader
                        name={template.name}
                        sorted={sorted}
                        readonly={props.readonly}
                        board={board}
                        activeView={activeView}
                        cards={cards}
                        views={views}
                        template={template}
                        key={template.id}
                        onDrop={onDropToColumn}
                        onAutoSizeColumn={onAutoSizeColumn}
                    />
                )
            })}
        </div>
    )
}

export default TableHeaders