Spaces:
Sleeping
Sleeping
| // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. | |
| // See LICENSE.txt for license information. | |
| import React, {useCallback} from 'react' | |
| import {FormattedMessage} from 'react-intl' | |
| import {IPropertyOption, IPropertyTemplate, Board, BoardGroup} from '../../blocks/board' | |
| import {createBoardView, BoardView} from '../../blocks/boardView' | |
| import {Card} from '../../blocks/card' | |
| import {Constants, Permission} from '../../constants' | |
| import mutator from '../../mutator' | |
| import {Utils} from '../../utils' | |
| import {useAppDispatch} from '../../store/hooks' | |
| import {updateView} from '../../store/views' | |
| import {useHasCurrentBoardPermissions} from '../../hooks/permissions' | |
| import BoardPermissionGate from '../permissions/boardPermissionGate' | |
| import './table.scss' | |
| import HiddenCardCount from '../../components/hiddenCardCount/hiddenCardCount' | |
| import TableHeaders from './tableHeaders' | |
| import TableRows from './tableRows' | |
| import TableGroup from './tableGroup' | |
| import CalculationRow from './calculation/calculationRow' | |
| import {ColumnResizeProvider} from './tableColumnResizeContext' | |
| type Props = { | |
| selectedCardIds: string[] | |
| board: Board | |
| cards: Card[] | |
| activeView: BoardView | |
| views: BoardView[] | |
| visibleGroups: BoardGroup[] | |
| groupByProperty?: IPropertyTemplate | |
| readonly: boolean | |
| cardIdToFocusOnRender: string | |
| showCard: (cardId?: string) => void | |
| addCard: (groupByOptionId?: string) => Promise<void> | |
| onCardClicked: (e: React.MouseEvent, card: Card) => void | |
| hiddenCardsCount: number | |
| showHiddenCardCountNotification: (show: boolean) => void | |
| } | |
| const Table = (props: Props): JSX.Element => { | |
| const {board, cards, activeView, visibleGroups, groupByProperty, views, hiddenCardsCount} = props | |
| const isManualSort = activeView.fields.sortOptions?.length === 0 | |
| const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties]) | |
| const canEditCards = useHasCurrentBoardPermissions([Permission.ManageBoardCards]) | |
| const dispatch = useAppDispatch() | |
| const resizeColumn = useCallback(async (columnId: string, width: number) => { | |
| const columnWidths = {...activeView.fields.columnWidths} | |
| const newWidth = Math.max(Constants.minColumnWidth, width) | |
| if (newWidth !== columnWidths[columnId]) { | |
| Utils.log(`Resize of column finished: prev=${columnWidths[columnId]}, new=${newWidth}`) | |
| columnWidths[columnId] = newWidth | |
| const newView = createBoardView(activeView) | |
| newView.fields.columnWidths = columnWidths | |
| try { | |
| dispatch(updateView(newView)) | |
| await mutator.updateBlock(board.id, newView, activeView, 'resize column') | |
| } catch { | |
| dispatch(updateView(activeView)) | |
| } | |
| } | |
| }, [activeView]) | |
| const hideGroup = useCallback((groupById: string): void => { | |
| const index: number = activeView.fields.collapsedOptionIds.indexOf(groupById) | |
| const newValue: string[] = [...activeView.fields.collapsedOptionIds] | |
| if (index > -1) { | |
| newValue.splice(index, 1) | |
| } else if (groupById !== '') { | |
| newValue.push(groupById) | |
| } | |
| const newView = createBoardView(activeView) | |
| newView.fields.collapsedOptionIds = newValue | |
| mutator.performAsUndoGroup(async () => { | |
| await mutator.updateBlock(board.id, newView, activeView, 'hide group') | |
| }) | |
| }, [activeView]) | |
| const onDropToGroupHeader = useCallback(async (option: IPropertyOption, dstOption?: IPropertyOption) => { | |
| if (dstOption) { | |
| Utils.log(`ondrop. Header target: ${dstOption.value}, source: ${option?.value}`) | |
| // Move option to new index | |
| const visibleOptionIds = visibleGroups.map((o) => o.option.id) | |
| const srcIndex = visibleOptionIds.indexOf(dstOption.id) | |
| const destIndex = visibleOptionIds.indexOf(option.id) | |
| visibleOptionIds.splice(srcIndex, 0, visibleOptionIds.splice(destIndex, 1)[0]) | |
| Utils.log(`ondrop. updated visibleoptionids: ${visibleOptionIds}`) | |
| await mutator.changeViewVisibleOptionIds(board.id, activeView.id, activeView.fields.visibleOptionIds, visibleOptionIds) | |
| } | |
| }, [activeView, visibleGroups]) | |
| const onDropToCard = useCallback((srcCard: Card, dstCard: Card) => { | |
| Utils.log(`onDropToCard: ${dstCard.title}`) | |
| onDropToGroup(srcCard, dstCard.fields.properties[activeView.fields.groupById!] as string, dstCard.id) | |
| }, [activeView.fields.groupById, cards]) | |
| const onDropToGroup = useCallback((srcCard: Card, groupID: string, dstCardID: string) => { | |
| Utils.log(`onDropToGroup: ${srcCard.title}`) | |
| const {selectedCardIds} = props | |
| const draggedCardIds = Array.from(new Set(selectedCardIds).add(srcCard.id)) | |
| const description = draggedCardIds.length > 1 ? `drag ${draggedCardIds.length} cards` : 'drag card' | |
| if (activeView.fields.groupById !== undefined) { | |
| const cardsById: { [key: string]: Card } = cards.reduce((acc: { [key: string]: Card }, card: Card): { [key: string]: Card } => { | |
| acc[card.id] = card | |
| return acc | |
| }, {}) | |
| const draggedCards: Card[] = draggedCardIds.map((o: string) => cardsById[o]) | |
| mutator.performAsUndoGroup(async () => { | |
| // Update properties of dragged cards | |
| const awaits = [] | |
| for (const draggedCard of draggedCards) { | |
| Utils.log(`draggedCard: ${draggedCard.title}, column: ${draggedCard.fields.properties}`) | |
| Utils.log(`droppedColumn: ${groupID}`) | |
| const oldOptionId = draggedCard.fields.properties[groupByProperty!.id] | |
| Utils.log(`ondrop. oldValue: ${oldOptionId}`) | |
| if (groupID !== oldOptionId) { | |
| awaits.push(mutator.changePropertyValue(board.id, draggedCard, groupByProperty!.id, groupID, description)) | |
| } | |
| } | |
| await Promise.all(awaits) | |
| }) | |
| } | |
| // Update dstCard order | |
| if (isManualSort) { | |
| let cardOrder = Array.from(new Set([...activeView.fields.cardOrder, ...cards.map((o) => o.id)])) | |
| if (dstCardID) { | |
| const isDraggingDown = cardOrder.indexOf(srcCard.id) <= cardOrder.indexOf(dstCardID) | |
| cardOrder = cardOrder.filter((id) => !draggedCardIds.includes(id)) | |
| let destIndex = cardOrder.indexOf(dstCardID) | |
| if (isDraggingDown) { | |
| destIndex += 1 | |
| } | |
| cardOrder.splice(destIndex, 0, ...draggedCardIds) | |
| } else { | |
| // Find index of first group item | |
| const firstCard = cards.find((card) => card.fields.properties[activeView.fields.groupById!] === groupID) | |
| if (firstCard) { | |
| const destIndex = cardOrder.indexOf(firstCard.id) | |
| cardOrder.splice(destIndex, 0, ...draggedCardIds) | |
| } else { | |
| // if not found, this is the only item in group. | |
| return | |
| } | |
| } | |
| mutator.performAsUndoGroup(async () => { | |
| await mutator.changeViewCardOrder(board.id, activeView.id, activeView.fields.cardOrder, cardOrder, description) | |
| }) | |
| } | |
| }, [activeView, cards, props.selectedCardIds, groupByProperty]) | |
| const propertyNameChanged = useCallback(async (option: IPropertyOption, text: string): Promise<void> => { | |
| await mutator.changePropertyOptionValue(board.id, board.cardProperties, groupByProperty!, option, text) | |
| }, [board, groupByProperty]) | |
| return ( | |
| <div className='Table'> | |
| <ColumnResizeProvider | |
| columnWidths={activeView.fields.columnWidths} | |
| onResizeColumn={resizeColumn} | |
| > | |
| <div className='octo-table-body'> | |
| <TableHeaders | |
| board={board} | |
| cards={cards} | |
| activeView={activeView} | |
| views={views} | |
| readonly={props.readonly || !canEditBoardProperties} | |
| /> | |
| {/* Table rows */} | |
| <div className='table-row-container'> | |
| {activeView.fields.groupById && | |
| visibleGroups.map((group) => { | |
| return ( | |
| <TableGroup | |
| key={group.option.id} | |
| board={board} | |
| activeView={activeView} | |
| groupByProperty={groupByProperty} | |
| group={group} | |
| readonly={props.readonly || !canEditCards} | |
| selectedCardIds={props.selectedCardIds} | |
| cardIdToFocusOnRender={props.cardIdToFocusOnRender} | |
| hideGroup={hideGroup} | |
| addCard={props.addCard} | |
| showCard={props.showCard} | |
| propertyNameChanged={propertyNameChanged} | |
| onCardClicked={props.onCardClicked} | |
| onDropToGroupHeader={onDropToGroupHeader} | |
| onDropToCard={onDropToCard} | |
| onDropToGroup={onDropToGroup} | |
| />) | |
| }) | |
| } | |
| {/* No Grouping, Rows, one per card */} | |
| {!activeView.fields.groupById && | |
| <TableRows | |
| board={board} | |
| activeView={activeView} | |
| cards={cards} | |
| selectedCardIds={props.selectedCardIds} | |
| readonly={props.readonly || !canEditCards} | |
| cardIdToFocusOnRender={props.cardIdToFocusOnRender} | |
| showCard={props.showCard} | |
| addCard={props.addCard} | |
| onCardClicked={props.onCardClicked} | |
| onDrop={onDropToCard} | |
| /> | |
| } | |
| </div> | |
| {/* Add New row */} | |
| <div className='octo-table-footer'> | |
| {!props.readonly && !activeView.fields.groupById && | |
| <BoardPermissionGate permissions={[Permission.ManageBoardCards]}> | |
| <div | |
| className='octo-table-cell' | |
| onClick={() => { | |
| props.addCard('') | |
| }} | |
| > | |
| <FormattedMessage | |
| id='TableComponent.plus-new' | |
| defaultMessage='+ New' | |
| /> | |
| </div> | |
| </BoardPermissionGate> | |
| } | |
| </div> | |
| <CalculationRow | |
| board={board} | |
| cards={cards} | |
| activeView={activeView} | |
| readonly={props.readonly || !canEditBoardProperties} | |
| /> | |
| </div> | |
| </ColumnResizeProvider> | |
| {hiddenCardsCount > 0 && | |
| <HiddenCardCount | |
| showHiddenCardNotification={props.showHiddenCardCountNotification} | |
| hiddenCardsCount={hiddenCardsCount} | |
| />} | |
| </div> | |
| ) | |
| } | |
| export default Table | |