1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-12-21 13:38:56 +02:00

making the grouped table list flat to prepare for virtualization

This commit is contained in:
Benjamin Cooke 2023-03-09 11:28:21 -05:00
parent 31b7734f85
commit 19007c486a
6 changed files with 106 additions and 57 deletions

View File

@ -188,27 +188,27 @@ const Table = (props: Props): JSX.Element => {
{/* Table rows */} {/* Table rows */}
<div className='table-row-container'> <div className='table-row-container'>
{activeView.fields.groupById && {activeView.fields.groupById &&
visibleGroups.map((group) => { visibleGroups.map((group) => {
return ( return (
<TableGroup <TableGroup
key={group.option.id} key={group.option.id}
board={board} board={board}
activeView={activeView} activeView={activeView}
groupByProperty={groupByProperty} groupByProperty={groupByProperty}
group={group} group={group}
readonly={props.readonly || !canEditCards} readonly={props.readonly || !canEditCards}
selectedCardIds={props.selectedCardIds} selectedCardIds={props.selectedCardIds}
cardIdToFocusOnRender={props.cardIdToFocusOnRender} cardIdToFocusOnRender={props.cardIdToFocusOnRender}
hideGroup={hideGroup} hideGroup={hideGroup}
addCard={props.addCard} addCard={props.addCard}
showCard={props.showCard} showCard={props.showCard}
propertyNameChanged={propertyNameChanged} propertyNameChanged={propertyNameChanged}
onCardClicked={props.onCardClicked} onCardClicked={props.onCardClicked}
onDropToGroupHeader={onDropToGroupHeader} onDropToGroupHeader={onDropToGroupHeader}
onDropToCard={onDropToCard} onDropToCard={onDropToCard}
onDropToGroup={onDropToGroup} onDropToGroup={onDropToGroup}
/>) />)
}) })
} }
{/* No Grouping, Rows, one per card */} {/* No Grouping, Rows, one per card */}
@ -224,6 +224,7 @@ const Table = (props: Props): JSX.Element => {
addCard={props.addCard} addCard={props.addCard}
onCardClicked={props.onCardClicked} onCardClicked={props.onCardClicked}
onDrop={onDropToCard} onDrop={onDropToCard}
useVirtualizedList={true}
/> />
} }
</div> </div>

View File

@ -32,31 +32,9 @@ type Props = {
const TableGroup = (props: Props): JSX.Element => { const TableGroup = (props: Props): JSX.Element => {
const {board, activeView, group, onDropToGroup, groupByProperty} = props const {board, activeView, group, onDropToGroup, groupByProperty} = props
const groupId = group.option.id
const [{isOver}, drop] = useDrop(() => ({
accept: 'card',
collect: (monitor) => ({
isOver: monitor.isOver(),
}),
drop: (item: Card, monitor) => {
if (monitor.isOver({shallow: true})) {
onDropToGroup(item, groupId, '')
}
},
}), [onDropToGroup, groupId])
let className = 'octo-table-group'
if (isOver) {
className += ' dragover'
}
return ( return (
<div <>
ref={drop}
className={className}
key={group.option.id}
>
<TableGroupHeaderRow <TableGroupHeaderRow
group={group} group={group}
board={board} board={board}
@ -67,6 +45,8 @@ const TableGroup = (props: Props): JSX.Element => {
readonly={props.readonly} readonly={props.readonly}
propertyNameChanged={props.propertyNameChanged} propertyNameChanged={props.propertyNameChanged}
onDrop={props.onDropToGroupHeader} onDrop={props.onDropToGroupHeader}
key={group.option.id}
onDropToGroup={onDropToGroup}
/> />
{(group.cards.length > 0) && {(group.cards.length > 0) &&
@ -81,8 +61,10 @@ const TableGroup = (props: Props): JSX.Element => {
addCard={props.addCard} addCard={props.addCard}
onCardClicked={props.onCardClicked} onCardClicked={props.onCardClicked}
onDrop={props.onDropToCard} onDrop={props.onDropToCard}
useVirtualizedList={false}
/>} />}
</div> </>
) )
} }

View File

@ -71,6 +71,7 @@ test('should match snapshot, no groups', async () => {
type: 'text', type: 'text',
options: [{id: 'property1', value: 'Property 1', color: ''}], options: [{id: 'property1', value: 'Property 1', color: ''}],
}} }}
onDropToGroup={jest.fn()}
/> />
</Wrapper>, </Wrapper>,
) )
@ -89,6 +90,7 @@ test('should match snapshot with Group', async () => {
addCard={jest.fn()} addCard={jest.fn()}
propertyNameChanged={jest.fn()} propertyNameChanged={jest.fn()}
onDrop={jest.fn()} onDrop={jest.fn()}
onDropToGroup={jest.fn()}
/> />
</Wrapper>, </Wrapper>,
) )
@ -107,6 +109,7 @@ test('should match snapshot on read only', async () => {
addCard={jest.fn()} addCard={jest.fn()}
propertyNameChanged={jest.fn()} propertyNameChanged={jest.fn()}
onDrop={jest.fn()} onDrop={jest.fn()}
onDropToGroup={jest.fn()}
/> />
</Wrapper>, </Wrapper>,
) )
@ -130,6 +133,7 @@ test('should match snapshot, hide group', async () => {
addCard={jest.fn()} addCard={jest.fn()}
propertyNameChanged={jest.fn()} propertyNameChanged={jest.fn()}
onDrop={jest.fn()} onDrop={jest.fn()}
onDropToGroup={jest.fn()}
/> />
</Wrapper>, </Wrapper>,
) )
@ -158,6 +162,7 @@ test('should match snapshot, add new', async () => {
addCard={addNew} addCard={addNew}
propertyNameChanged={jest.fn()} propertyNameChanged={jest.fn()}
onDrop={jest.fn()} onDrop={jest.fn()}
onDropToGroup={jest.fn()}
/> />
</Wrapper>, </Wrapper>,
) )
@ -184,6 +189,7 @@ test('should match snapshot, edit title', async () => {
addCard={jest.fn()} addCard={jest.fn()}
propertyNameChanged={jest.fn()} propertyNameChanged={jest.fn()}
onDrop={jest.fn()} onDrop={jest.fn()}
onDropToGroup={jest.fn()}
/> />
</Wrapper>, </Wrapper>,
) )

View File

@ -1,13 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
/* eslint-disable max-lines */ /* eslint-disable max-lines */
import React, {useState, useEffect} from 'react' import React, {useState, useEffect, useRef} from 'react'
import {FormattedMessage, useIntl} from 'react-intl' import {FormattedMessage, useIntl} from 'react-intl'
import {useDrag, useDrop} from 'react-dnd'
import {Constants} from '../../constants' import {Constants} from '../../constants'
import {IPropertyOption, Board, IPropertyTemplate, BoardGroup} from '../../blocks/board' import {IPropertyOption, Board, IPropertyTemplate, BoardGroup} from '../../blocks/board'
import {BoardView} from '../../blocks/boardView' import {BoardView} from '../../blocks/boardView'
import {useSortable} from '../../hooks/sortable' import {Card} from '../../blocks/card'
import mutator from '../../mutator' import mutator from '../../mutator'
import Button from '../../widgets/buttons/button' import Button from '../../widgets/buttons/button'
import IconButton from '../../widgets/buttons/iconButton' import IconButton from '../../widgets/buttons/iconButton'
@ -33,13 +34,42 @@ type Props = {
addCard: (groupByOptionId?: string) => Promise<void> addCard: (groupByOptionId?: string) => Promise<void>
propertyNameChanged: (option: IPropertyOption, text: string) => Promise<void> propertyNameChanged: (option: IPropertyOption, text: string) => Promise<void>
onDrop: (srcOption: IPropertyOption, dstOption?: IPropertyOption) => void onDrop: (srcOption: IPropertyOption, dstOption?: IPropertyOption) => void
onDropToGroup: (srcCard: Card, groupID: string, dstCardID: string) => void
} }
const TableGroupHeaderRow = (props: Props): JSX.Element => { const TableGroupHeaderRow = (props: Props): JSX.Element => {
const {board, activeView, group, groupByProperty} = props const {board, activeView, group, groupByProperty} = props
const [groupTitle, setGroupTitle] = useState(group.option.value) const [groupTitle, setGroupTitle] = useState(group.option.value)
const [isDragging, isOver, groupHeaderRef] = useSortable('groupHeader', group.option, !props.readonly, props.onDrop) const ref = useRef<HTMLDivElement>(null)
const [{isDragging}, drag] = useDrag(() => ({
type: 'groupHeader',
item: group.option,
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
canDrag: () => !props.readonly,
}), ['groupHeader', group.option, props.readonly])
const [{isOver}, drop] = useDrop(() => ({
accept: ['groupHeader', 'card'],
collect: (monitor) => ({
isOver: monitor.isOver(),
}),
drop: (dragItem: IPropertyOption | Card, monitor) => {
// @ts-ignore
if (dragItem?.type === 'card' && monitor.isOver({shallow: true})) {
// @ts-ignore
props.onDropToGroup(dragItem, group.option.id, '')
return;
}
// @ts-ignore
props.onDrop(dragItem, group.option)
},
canDrop: () => !props.readonly,
}), [group.option, props.onDrop, props.readonly])
drop(drag(ref))
const intl = useIntl() const intl = useIntl()
const columnResize = useColumnResize() const columnResize = useColumnResize()
@ -59,7 +89,7 @@ const TableGroupHeaderRow = (props: Props): JSX.Element => {
return ( return (
<div <div
key={group.option.id + 'header'} key={group.option.id + 'header'}
ref={groupHeaderRef} ref={ref}
style={{opacity: isDragging ? 0.5 : 1}} style={{opacity: isDragging ? 0.5 : 1}}
className={className} className={className}
> >

View File

@ -74,6 +74,7 @@ describe('components/table/TableRows', () => {
addCard={addCard} addCard={addCard}
onCardClicked={jest.fn()} onCardClicked={jest.fn()}
onDrop={jest.fn()} onDrop={jest.fn()}
useVirtualizedList={false}
/> />
</ColumnResizeProvider> </ColumnResizeProvider>
</ReduxProvider>, </ReduxProvider>,

View File

@ -1,8 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React, {useCallback} from 'react' import React, {useCallback} from 'react'
import {FixedSizeList, ListChildComponentProps} from 'react-window'; import {FixedSizeList, ListChildComponentProps} from 'react-window'
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer'
import {Card} from '../../blocks/card' import {Card} from '../../blocks/card'
import {Board} from '../../blocks/board' import {Board} from '../../blocks/board'
@ -23,21 +23,50 @@ type Props = {
addCard: (groupByOptionId?: string) => Promise<void> addCard: (groupByOptionId?: string) => Promise<void>
onCardClicked: (e: React.MouseEvent, card: Card) => void onCardClicked: (e: React.MouseEvent, card: Card) => void
onDrop: (srcCard: Card, dstCard: Card) => void onDrop: (srcCard: Card, dstCard: Card) => void
useVirtualizedList: boolean
} }
const TableRows = (props: Props): JSX.Element => { const TableRows = (props: Props): JSX.Element => {
const {board, cards, activeView} = props const {board, cards, activeView, useVirtualizedList} = props
const onClickRow = useCallback((e: React.MouseEvent<HTMLDivElement>, card: Card) => { const onClickRow = useCallback((e: React.MouseEvent<HTMLDivElement>, card: Card) => {
props.onCardClicked(e, card) props.onCardClicked(e, card)
}, [props.onCardClicked]) }, [props.onCardClicked])
if (!useVirtualizedList) {
return (
<>
{cards.map((card, idx) => {
return (
<TableRow
key={card.id + card.updateAt}
board={board}
columnWidths={activeView.fields.columnWidths}
isManualSort={activeView.fields.sortOptions.length === 0}
groupById={activeView.fields.groupById}
visiblePropertyIds={activeView.fields.visiblePropertyIds}
collapsedOptionIds={activeView.fields.collapsedOptionIds}
card={card}
addCard={props.addCard}
isSelected={props.selectedCardIds.includes(card.id)}
focusOnMount={props.cardIdToFocusOnRender === card.id}
isLastCard={idx === (cards.length - 1)}
onClick={onClickRow}
showCard={props.showCard}
readonly={props.readonly}
onDrop={props.onDrop}
/>
)
})}
</>
)
}
const isItemLoaded = (index: number) => { const isItemLoaded = (index: number) => {
return index < cards.length; return index < cards.length;
}; }
const Item = ({index, style}: ListChildComponentProps) => { const Item = ({index, style}: ListChildComponentProps) => {
const card = cards[index] const card = cards[index]
if (isItemLoaded(index)) { if (isItemLoaded(index)) {
return ( return (
@ -72,13 +101,13 @@ const TableRows = (props: Props): JSX.Element => {
} }
return ( return (
<AutoSizer> <AutoSizer disableWidth>
{({height, width}) => ( {({height}) => (
<FixedSizeList <FixedSizeList
height={height} height={height}
itemCount={1828} itemCount={1828}
itemSize={44} itemSize={44}
width={width} width={'100%'}
> >
{Item} {Item}
</FixedSizeList> </FixedSizeList>