2020-10-20 21:50:53 +02:00
|
|
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
|
|
// See LICENSE.txt for license information.
|
2020-10-20 21:52:56 +02:00
|
|
|
import React from 'react'
|
2020-10-23 19:36:20 +02:00
|
|
|
import {FormattedMessage} from 'react-intl'
|
2020-10-20 21:52:56 +02:00
|
|
|
|
|
|
|
import {Archiver} from '../archiver'
|
2020-10-24 00:32:09 +02:00
|
|
|
import {ISortOption} from '../blocks/boardView'
|
2020-10-20 21:52:56 +02:00
|
|
|
import {BlockIcons} from '../blockIcons'
|
|
|
|
import {IPropertyTemplate} from '../blocks/board'
|
2020-10-21 03:28:55 +02:00
|
|
|
import {Card, MutableCard} from '../blocks/card'
|
2020-10-21 03:54:39 +02:00
|
|
|
import {BoardTree} from '../viewModel/boardTree'
|
2020-10-20 21:52:56 +02:00
|
|
|
import ViewMenu from '../components/viewMenu'
|
|
|
|
import {CsvExporter} from '../csvExporter'
|
|
|
|
import {Menu as OldMenu} from '../menu'
|
|
|
|
import mutator from '../mutator'
|
|
|
|
import {Utils} from '../utils'
|
|
|
|
import Menu from '../widgets/menu'
|
|
|
|
import MenuWrapper from '../widgets/menuWrapper'
|
|
|
|
|
|
|
|
import Button from './button'
|
|
|
|
import {CardDialog} from './cardDialog'
|
|
|
|
import {Editable} from './editable'
|
|
|
|
import RootPortal from './rootPortal'
|
|
|
|
import {TableRow} from './tableRow'
|
2020-10-08 18:21:27 +02:00
|
|
|
|
|
|
|
type Props = {
|
2020-10-20 21:50:53 +02:00
|
|
|
boardTree?: BoardTree
|
|
|
|
showView: (id: string) => void
|
|
|
|
showFilter: (el: HTMLElement) => void
|
|
|
|
setSearchText: (text: string) => void
|
2020-10-08 18:21:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type State = {
|
2020-10-20 21:50:53 +02:00
|
|
|
isHoverOnCover: boolean
|
|
|
|
isSearching: boolean
|
|
|
|
shownCard?: Card
|
|
|
|
viewMenu: boolean
|
2020-10-08 18:21:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class TableComponent extends React.Component<Props, State> {
|
2020-10-20 21:50:53 +02:00
|
|
|
private draggedHeaderTemplate: IPropertyTemplate
|
|
|
|
private cardIdToRowMap = new Map<string, React.RefObject<TableRow>>()
|
|
|
|
private cardIdToFocusOnRender: string
|
|
|
|
private searchFieldRef = React.createRef<Editable>()
|
|
|
|
|
|
|
|
constructor(props: Props) {
|
|
|
|
super(props)
|
|
|
|
this.state = {isHoverOnCover: false, isSearching: Boolean(this.props.boardTree?.getSearchText()), viewMenu: false}
|
|
|
|
}
|
|
|
|
|
2020-10-22 23:10:38 +02:00
|
|
|
shouldComponentUpdate(): boolean {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevPros: Props, prevState: State): void {
|
2020-10-22 00:03:12 +02:00
|
|
|
if (this.state.isSearching && !prevState.isSearching) {
|
2020-10-20 21:50:53 +02:00
|
|
|
this.searchFieldRef.current.focus()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-22 23:10:38 +02:00
|
|
|
render(): JSX.Element {
|
2020-10-22 00:03:12 +02:00
|
|
|
const {boardTree, showView} = this.props
|
2020-10-20 21:50:53 +02:00
|
|
|
|
2020-10-22 00:03:12 +02:00
|
|
|
if (!boardTree || !boardTree.board) {
|
2020-10-20 21:50:53 +02:00
|
|
|
return (
|
2020-10-23 19:36:20 +02:00
|
|
|
<div>
|
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.loading'
|
|
|
|
defaultMessage='Loading...'
|
|
|
|
/>
|
|
|
|
</div>
|
2020-10-22 00:03:12 +02:00
|
|
|
)
|
|
|
|
}
|
2020-10-20 21:50:53 +02:00
|
|
|
|
2020-10-22 00:03:12 +02:00
|
|
|
const {board, cards, activeView} = boardTree
|
2020-10-20 21:50:53 +02:00
|
|
|
|
|
|
|
const hasFilter = activeView.filter && activeView.filter.filters?.length > 0
|
2020-10-22 00:03:12 +02:00
|
|
|
const hasSort = activeView.sortOptions.length > 0
|
2020-10-20 21:50:53 +02:00
|
|
|
|
2020-10-22 00:03:12 +02:00
|
|
|
this.cardIdToRowMap.clear()
|
2020-10-20 21:50:53 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<div className='octo-app'>
|
|
|
|
{this.state.shownCard &&
|
|
|
|
<RootPortal>
|
|
|
|
<CardDialog
|
|
|
|
boardTree={boardTree}
|
|
|
|
card={this.state.shownCard}
|
|
|
|
onClose={() => this.setState({shownCard: undefined})}
|
|
|
|
/>
|
|
|
|
</RootPortal>}
|
|
|
|
<div className='octo-frame'>
|
|
|
|
<div
|
|
|
|
className='octo-hovercontrols'
|
|
|
|
onMouseOver={() => {
|
|
|
|
this.setState({...this.state, isHoverOnCover: true})
|
|
|
|
}}
|
|
|
|
onMouseLeave={() => {
|
|
|
|
this.setState({...this.state, isHoverOnCover: false})
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Button
|
|
|
|
style={{display: (!board.icon && this.state.isHoverOnCover) ? null : 'none'}}
|
|
|
|
onClick={() => {
|
2020-10-22 00:03:12 +02:00
|
|
|
const newIcon = BlockIcons.shared.randomIcon()
|
2020-10-20 21:50:53 +02:00
|
|
|
mutator.changeIcon(board, newIcon)
|
|
|
|
}}
|
2020-10-23 19:36:20 +02:00
|
|
|
>
|
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.add-icon'
|
|
|
|
defaultMessage='Add Icon'
|
|
|
|
/>
|
|
|
|
</Button>
|
2020-10-20 21:50:53 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className='octo-icontitle'>
|
2020-10-23 19:36:20 +02:00
|
|
|
{board.icon &&
|
2020-10-20 21:50:53 +02:00
|
|
|
<MenuWrapper>
|
|
|
|
<div className='octo-button octo-icon'>{board.icon}</div>
|
|
|
|
<Menu>
|
2020-10-23 19:36:20 +02:00
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.random-icon'
|
|
|
|
defaultMessage='Random'
|
|
|
|
>
|
|
|
|
{(text: string) => (
|
|
|
|
<Menu.Text
|
|
|
|
id='random'
|
|
|
|
name={text}
|
|
|
|
onClick={() => mutator.changeIcon(board, BlockIcons.shared.randomIcon())}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</FormattedMessage>
|
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.remove-icon'
|
|
|
|
defaultMessage='Remove Icon'
|
|
|
|
>
|
|
|
|
{(text: string) => (
|
|
|
|
<Menu.Text
|
|
|
|
id='remove'
|
|
|
|
name={text}
|
|
|
|
onClick={() => mutator.changeIcon(board, undefined, 'remove icon')}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</FormattedMessage>
|
2020-10-20 21:50:53 +02:00
|
|
|
</Menu>
|
2020-10-23 19:36:20 +02:00
|
|
|
</MenuWrapper>}
|
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.remove-icon'
|
|
|
|
defaultMessage='Remove Icon'
|
|
|
|
>
|
|
|
|
{(placeholder: string) => (
|
|
|
|
<Editable
|
|
|
|
className='title'
|
|
|
|
text={board.title}
|
|
|
|
placeholderText={placeholder}
|
|
|
|
onChanged={(text) => {
|
|
|
|
mutator.changeTitle(board, text)
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</FormattedMessage>
|
2020-10-20 21:50:53 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className='octo-table'>
|
|
|
|
<div className='octo-controls'>
|
|
|
|
<Editable
|
|
|
|
style={{color: '#000000', fontWeight: 600}}
|
|
|
|
text={activeView.title}
|
|
|
|
placeholderText='Untitled View'
|
|
|
|
onChanged={(text) => {
|
|
|
|
mutator.changeTitle(activeView, text)
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<MenuWrapper>
|
|
|
|
<div
|
|
|
|
className='octo-button'
|
|
|
|
style={{color: '#000000', fontWeight: 600}}
|
|
|
|
>
|
|
|
|
<div className='imageDropdown'/>
|
|
|
|
</div>
|
|
|
|
<ViewMenu
|
|
|
|
board={board}
|
|
|
|
boardTree={boardTree}
|
|
|
|
showView={showView}
|
|
|
|
/>
|
|
|
|
</MenuWrapper>
|
|
|
|
<div className='octo-spacer'/>
|
2020-10-24 08:21:45 +02:00
|
|
|
<MenuWrapper>
|
|
|
|
<div className={'octo-button'}>
|
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.properties'
|
|
|
|
defaultMessage='Properties'
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<Menu>
|
|
|
|
{boardTree.board.cardProperties.map((option) => (
|
|
|
|
<Menu.Switch
|
|
|
|
id={option.id}
|
|
|
|
name={option.name}
|
|
|
|
isOn={activeView.visiblePropertyIds.includes(option.id)}
|
|
|
|
onClick={(propertyId: string) => {
|
|
|
|
const property = boardTree.board.cardProperties.find((o) => o.id === propertyId)
|
|
|
|
Utils.assertValue(property)
|
|
|
|
Utils.log(`Toggle property ${property.name}`)
|
|
|
|
|
|
|
|
let newVisiblePropertyIds = []
|
|
|
|
if (activeView.visiblePropertyIds.includes(propertyId)) {
|
|
|
|
newVisiblePropertyIds = activeView.visiblePropertyIds.filter((o) => o !== propertyId)
|
|
|
|
} else {
|
|
|
|
newVisiblePropertyIds = [...activeView.visiblePropertyIds, propertyId]
|
|
|
|
}
|
|
|
|
mutator.changeViewVisibleProperties(activeView, newVisiblePropertyIds)
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</Menu>
|
|
|
|
</MenuWrapper>
|
2020-10-20 21:50:53 +02:00
|
|
|
<div
|
|
|
|
className={hasFilter ? 'octo-button active' : 'octo-button'}
|
|
|
|
onClick={(e) => {
|
|
|
|
this.filterClicked(e)
|
|
|
|
}}
|
2020-10-23 19:36:20 +02:00
|
|
|
>
|
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.filter'
|
|
|
|
defaultMessage='Filter'
|
|
|
|
/>
|
|
|
|
</div>
|
2020-10-24 00:32:09 +02:00
|
|
|
<MenuWrapper>
|
|
|
|
<div className={hasSort ? 'octo-button active' : 'octo-button'}>
|
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.sort'
|
|
|
|
defaultMessage='Sort'
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<Menu>
|
|
|
|
{boardTree.board.cardProperties.map((option) => (
|
|
|
|
<Menu.Text
|
|
|
|
id={option.id}
|
|
|
|
name={option.name}
|
|
|
|
icon={(activeView.sortOptions[0]?.propertyId === option.id) ? activeView.sortOptions[0].reversed ? 'sortUp' : 'sortDown' : undefined}
|
|
|
|
onClick={(propertyId: string) => {
|
|
|
|
let newSortOptions: ISortOption[] = []
|
|
|
|
if (activeView.sortOptions[0] && activeView.sortOptions[0].propertyId === propertyId) {
|
|
|
|
// Already sorting by name, so reverse it
|
|
|
|
newSortOptions = [
|
|
|
|
{propertyId, reversed: !activeView.sortOptions[0].reversed},
|
|
|
|
]
|
|
|
|
} else {
|
|
|
|
newSortOptions = [
|
|
|
|
{propertyId, reversed: false},
|
|
|
|
]
|
|
|
|
}
|
|
|
|
mutator.changeViewSortOptions(activeView, newSortOptions)
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</Menu>
|
|
|
|
</MenuWrapper>
|
2020-10-23 19:36:20 +02:00
|
|
|
{this.state.isSearching &&
|
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.search-text'
|
|
|
|
defaultMessage='Search text'
|
|
|
|
>
|
|
|
|
{(placeholder: string) => (
|
|
|
|
<Editable
|
|
|
|
ref={this.searchFieldRef}
|
|
|
|
text={boardTree.getSearchText()}
|
|
|
|
placeholderText={placeholder}
|
|
|
|
style={{color: '#000000'}}
|
|
|
|
onChanged={(text) => {
|
|
|
|
this.searchChanged(text)
|
|
|
|
}}
|
|
|
|
onKeyDown={(e) => {
|
|
|
|
this.onSearchKeyDown(e)
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</FormattedMessage>}
|
|
|
|
{!this.state.isSearching &&
|
2020-10-20 21:50:53 +02:00
|
|
|
<div
|
|
|
|
className='octo-button'
|
|
|
|
onClick={() => {
|
|
|
|
this.setState({...this.state, isSearching: true})
|
|
|
|
}}
|
2020-10-23 19:36:20 +02:00
|
|
|
>
|
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.search'
|
|
|
|
defaultMessage='Search'
|
|
|
|
/>
|
|
|
|
</div>}
|
2020-10-20 21:50:53 +02:00
|
|
|
<div
|
|
|
|
className='octo-button'
|
|
|
|
onClick={(e) => this.optionsClicked(e)}
|
|
|
|
><div className='imageOptions'/></div>
|
|
|
|
<div
|
|
|
|
className='octo-button filled'
|
|
|
|
onClick={() => {
|
|
|
|
this.addCard(true)
|
|
|
|
}}
|
2020-10-23 19:36:20 +02:00
|
|
|
>
|
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.new'
|
|
|
|
defaultMessage='New'
|
|
|
|
/>
|
|
|
|
</div>
|
2020-10-20 21:50:53 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* Main content */}
|
|
|
|
|
|
|
|
<div className='octo-table-body'>
|
|
|
|
|
|
|
|
{/* Headers */}
|
|
|
|
|
|
|
|
<div
|
|
|
|
className='octo-table-header'
|
|
|
|
id='mainBoardHeader'
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
className='octo-table-cell title-cell'
|
|
|
|
id='mainBoardHeader'
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
className='octo-label'
|
|
|
|
style={{cursor: 'pointer'}}
|
|
|
|
onClick={(e) => {
|
|
|
|
this.headerClicked(e, '__name')
|
|
|
|
}}
|
2020-10-23 19:36:20 +02:00
|
|
|
>
|
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.name'
|
|
|
|
defaultMessage='Name'
|
|
|
|
/>
|
|
|
|
</div>
|
2020-10-20 21:50:53 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
{board.cardProperties.
|
|
|
|
filter((template) => activeView.visiblePropertyIds.includes(template.id)).
|
|
|
|
map((template) =>
|
|
|
|
(<div
|
|
|
|
key={template.id}
|
|
|
|
className='octo-table-cell'
|
|
|
|
|
|
|
|
draggable={true}
|
|
|
|
onDragStart={() => {
|
|
|
|
this.draggedHeaderTemplate = template
|
|
|
|
}}
|
|
|
|
onDragEnd={() => {
|
|
|
|
this.draggedHeaderTemplate = undefined
|
|
|
|
}}
|
|
|
|
|
|
|
|
onDragOver={(e) => {
|
|
|
|
e.preventDefault(); (e.target as HTMLElement).classList.add('dragover')
|
|
|
|
}}
|
|
|
|
onDragEnter={(e) => {
|
|
|
|
e.preventDefault(); (e.target as HTMLElement).classList.add('dragover')
|
|
|
|
}}
|
|
|
|
onDragLeave={(e) => {
|
|
|
|
e.preventDefault(); (e.target as HTMLElement).classList.remove('dragover')
|
|
|
|
}}
|
|
|
|
onDrop={(e) => {
|
|
|
|
e.preventDefault(); (e.target as HTMLElement).classList.remove('dragover'); this.onDropToColumn(template)
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
className='octo-label'
|
|
|
|
style={{cursor: 'pointer'}}
|
|
|
|
onClick={(e) => {
|
|
|
|
this.headerClicked(e, template.id)
|
|
|
|
}}
|
|
|
|
>{template.name}</div>
|
|
|
|
</div>),
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* Rows, one per card */}
|
|
|
|
|
|
|
|
{cards.map((card) => {
|
|
|
|
const openButonRef = React.createRef<HTMLDivElement>()
|
|
|
|
const tableRowRef = React.createRef<TableRow>()
|
|
|
|
|
2020-10-22 00:03:12 +02:00
|
|
|
let focusOnMount = false
|
2020-10-20 21:50:53 +02:00
|
|
|
if (this.cardIdToFocusOnRender && this.cardIdToFocusOnRender === card.id) {
|
2020-10-22 00:03:12 +02:00
|
|
|
this.cardIdToFocusOnRender = undefined
|
2020-10-20 21:50:53 +02:00
|
|
|
focusOnMount = true
|
2020-10-22 00:03:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const tableRow = (<TableRow
|
|
|
|
key={card.id}
|
|
|
|
ref={tableRowRef}
|
|
|
|
boardTree={boardTree}
|
|
|
|
card={card}
|
|
|
|
focusOnMount={focusOnMount}
|
|
|
|
onKeyDown={(e) => {
|
2020-10-20 21:50:53 +02:00
|
|
|
if (e.keyCode === 13) {
|
|
|
|
// Enter: Insert new card if on last row
|
|
|
|
if (cards.length > 0 && cards[cards.length - 1] === card) {
|
|
|
|
this.addCard(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}}
|
2020-10-22 23:10:38 +02:00
|
|
|
/>)
|
2020-10-20 21:50:53 +02:00
|
|
|
|
|
|
|
this.cardIdToRowMap.set(card.id, tableRowRef)
|
|
|
|
|
|
|
|
return tableRow
|
|
|
|
})}
|
|
|
|
|
|
|
|
{/* Add New row */}
|
|
|
|
|
|
|
|
<div className='octo-table-footer'>
|
|
|
|
<div
|
|
|
|
className='octo-table-cell'
|
|
|
|
onClick={() => {
|
|
|
|
this.addCard()
|
|
|
|
}}
|
|
|
|
>
|
2020-10-23 19:36:20 +02:00
|
|
|
<FormattedMessage
|
|
|
|
id='TableComponent.plus-new'
|
|
|
|
defaultMessage='+ New'
|
|
|
|
/>
|
2020-10-20 21:50:53 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div >
|
|
|
|
</div >
|
2020-10-22 00:03:12 +02:00
|
|
|
)
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private filterClicked(e: React.MouseEvent) {
|
|
|
|
this.props.showFilter(e.target as HTMLElement)
|
|
|
|
}
|
|
|
|
|
|
|
|
private async optionsClicked(e: React.MouseEvent) {
|
|
|
|
const {boardTree} = this.props
|
|
|
|
|
2020-10-22 00:03:12 +02:00
|
|
|
OldMenu.shared.options = [
|
2020-10-20 21:50:53 +02:00
|
|
|
{id: 'exportCsv', name: 'Export to CSV'},
|
|
|
|
{id: 'exportBoardArchive', name: 'Export board archive'},
|
2020-10-22 00:03:12 +02:00
|
|
|
]
|
2020-10-20 21:50:53 +02:00
|
|
|
|
|
|
|
OldMenu.shared.onMenuClicked = async (id: string) => {
|
|
|
|
switch (id) {
|
|
|
|
case 'exportCsv': {
|
2020-10-22 00:03:12 +02:00
|
|
|
CsvExporter.exportTableCsv(boardTree)
|
2020-10-20 21:52:56 +02:00
|
|
|
break
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
2020-10-22 00:03:12 +02:00
|
|
|
case 'exportBoardArchive': {
|
|
|
|
Archiver.exportBoardTree(boardTree)
|
2020-10-20 21:52:56 +02:00
|
|
|
break
|
2020-10-22 00:03:12 +02:00
|
|
|
}
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
OldMenu.shared.showAtElement(e.target as HTMLElement)
|
|
|
|
}
|
|
|
|
|
|
|
|
private async headerClicked(e: React.MouseEvent<HTMLDivElement>, templateId: string) {
|
|
|
|
const {boardTree} = this.props
|
2020-10-22 00:03:12 +02:00
|
|
|
const {board} = boardTree
|
2020-10-20 21:50:53 +02:00
|
|
|
const {activeView} = boardTree
|
|
|
|
|
2020-10-22 00:03:12 +02:00
|
|
|
const options = [
|
2020-10-20 21:50:53 +02:00
|
|
|
{id: 'sortAscending', name: 'Sort ascending'},
|
|
|
|
{id: 'sortDescending', name: 'Sort descending'},
|
|
|
|
{id: 'insertLeft', name: 'Insert left'},
|
|
|
|
{id: 'insertRight', name: 'Insert right'},
|
|
|
|
]
|
|
|
|
|
2020-10-22 00:03:12 +02:00
|
|
|
if (templateId !== '__name') {
|
2020-10-20 21:50:53 +02:00
|
|
|
options.push({id: 'hide', name: 'Hide'})
|
2020-10-22 00:03:12 +02:00
|
|
|
options.push({id: 'duplicate', name: 'Duplicate'})
|
|
|
|
options.push({id: 'delete', name: 'Delete'})
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
OldMenu.shared.options = options
|
|
|
|
OldMenu.shared.onMenuClicked = async (optionId: string, type?: string) => {
|
2020-10-22 00:03:12 +02:00
|
|
|
switch (optionId) {
|
|
|
|
case 'sortAscending': {
|
|
|
|
const newSortOptions = [
|
2020-10-20 21:50:53 +02:00
|
|
|
{propertyId: templateId, reversed: false},
|
2020-10-22 00:03:12 +02:00
|
|
|
]
|
|
|
|
await mutator.changeViewSortOptions(activeView, newSortOptions)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
case 'sortDescending': {
|
|
|
|
const newSortOptions = [
|
|
|
|
{propertyId: templateId, reversed: true},
|
|
|
|
]
|
|
|
|
await mutator.changeViewSortOptions(activeView, newSortOptions)
|
2020-10-20 21:52:56 +02:00
|
|
|
break
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
2020-10-22 00:03:12 +02:00
|
|
|
case 'insertLeft': {
|
2020-10-20 21:50:53 +02:00
|
|
|
if (templateId !== '__name') {
|
2020-10-22 00:03:12 +02:00
|
|
|
const index = board.cardProperties.findIndex((o) => o.id === templateId)
|
|
|
|
await mutator.insertPropertyTemplate(boardTree, index)
|
2020-10-20 21:50:53 +02:00
|
|
|
} else {
|
2020-10-22 00:03:12 +02:00
|
|
|
// TODO: Handle name column
|
|
|
|
}
|
2020-10-20 21:50:53 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
case 'insertRight': {
|
|
|
|
if (templateId !== '__name') {
|
|
|
|
const index = board.cardProperties.findIndex((o) => o.id === templateId) + 1
|
2020-10-22 00:03:12 +02:00
|
|
|
await mutator.insertPropertyTemplate(boardTree, index)
|
|
|
|
} else {
|
|
|
|
// TODO: Handle name column
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
2020-10-22 00:03:12 +02:00
|
|
|
break
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
|
|
|
case 'duplicate': {
|
|
|
|
await mutator.duplicatePropertyTemplate(boardTree, templateId)
|
2020-10-22 00:03:12 +02:00
|
|
|
break
|
|
|
|
}
|
2020-10-20 21:50:53 +02:00
|
|
|
case 'hide': {
|
|
|
|
const newVisiblePropertyIds = activeView.visiblePropertyIds.filter((o) => o !== templateId)
|
|
|
|
await mutator.changeViewVisibleProperties(activeView, newVisiblePropertyIds)
|
2020-10-20 21:52:56 +02:00
|
|
|
break
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
|
|
|
case 'delete': {
|
|
|
|
await mutator.deleteProperty(boardTree, templateId)
|
2020-10-22 00:03:12 +02:00
|
|
|
break
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
|
|
|
default: {
|
|
|
|
Utils.assertFailure(`Unexpected menu option: ${optionId}`)
|
2020-10-20 21:52:56 +02:00
|
|
|
break
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
|
|
|
}
|
2020-10-22 00:03:12 +02:00
|
|
|
}
|
2020-10-20 21:50:53 +02:00
|
|
|
OldMenu.shared.showAtElement(e.target as HTMLElement)
|
|
|
|
}
|
|
|
|
|
2020-10-22 23:10:38 +02:00
|
|
|
private focusOnCardTitle(cardId: string): void {
|
2020-10-20 21:50:53 +02:00
|
|
|
const tableRowRef = this.cardIdToRowMap.get(cardId)
|
|
|
|
Utils.log(`focusOnCardTitle, ${tableRowRef?.current ?? 'undefined'}`)
|
2020-10-22 00:03:12 +02:00
|
|
|
tableRowRef?.current.focusOnTitle()
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
|
|
|
|
2020-10-22 23:10:38 +02:00
|
|
|
private async addCard(show = false) {
|
2020-10-20 21:50:53 +02:00
|
|
|
const {boardTree} = this.props
|
|
|
|
|
2020-10-22 00:03:12 +02:00
|
|
|
const card = new MutableCard()
|
|
|
|
card.parentId = boardTree.board.id
|
|
|
|
card.icon = BlockIcons.shared.randomIcon()
|
|
|
|
await mutator.insertBlock(
|
|
|
|
card,
|
2020-10-20 21:50:53 +02:00
|
|
|
'add card',
|
|
|
|
async () => {
|
|
|
|
if (show) {
|
|
|
|
this.setState({shownCard: card})
|
2020-10-22 00:03:12 +02:00
|
|
|
} else {
|
|
|
|
// Focus on this card's title inline on next render
|
2020-10-20 21:50:53 +02:00
|
|
|
this.cardIdToFocusOnRender = card.id
|
2020-10-22 00:03:12 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private async onDropToColumn(template: IPropertyTemplate) {
|
2020-10-22 00:03:12 +02:00
|
|
|
const {draggedHeaderTemplate} = this
|
2020-10-20 21:50:53 +02:00
|
|
|
if (!draggedHeaderTemplate) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-10-22 00:03:12 +02:00
|
|
|
const {boardTree} = this.props
|
2020-10-20 21:50:53 +02:00
|
|
|
const {board} = boardTree
|
|
|
|
|
2020-10-22 00:03:12 +02:00
|
|
|
Utils.assertValue(mutator)
|
2020-10-20 21:50:53 +02:00
|
|
|
Utils.assertValue(boardTree)
|
|
|
|
|
|
|
|
Utils.log(`ondrop. Source column: ${draggedHeaderTemplate.name}, dest column: ${template.name}`)
|
|
|
|
|
2020-10-22 00:03:12 +02:00
|
|
|
// Move template to new index
|
|
|
|
const destIndex = template ? board.cardProperties.indexOf(template) : 0
|
2020-10-20 21:50:53 +02:00
|
|
|
await mutator.changePropertyTemplateOrder(board, draggedHeaderTemplate, destIndex)
|
|
|
|
}
|
|
|
|
|
2020-10-22 23:10:38 +02:00
|
|
|
private onSearchKeyDown(e: React.KeyboardEvent) {
|
2020-10-22 00:03:12 +02:00
|
|
|
if (e.keyCode === 27) { // ESC: Clear search
|
2020-10-20 21:52:56 +02:00
|
|
|
this.searchFieldRef.current.text = ''
|
2020-10-20 21:50:53 +02:00
|
|
|
this.setState({...this.state, isSearching: false})
|
|
|
|
this.props.setSearchText(undefined)
|
|
|
|
e.preventDefault()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-22 23:10:38 +02:00
|
|
|
private searchChanged(text?: string) {
|
2020-10-22 00:03:12 +02:00
|
|
|
this.props.setSearchText(text)
|
2020-10-20 21:50:53 +02:00
|
|
|
}
|
2020-10-08 18:21:27 +02:00
|
|
|
}
|
|
|
|
|
2020-10-20 21:50:53 +02:00
|
|
|
export {TableComponent}
|