Creating a separate table component
This commit is contained in:
parent
7a50a9339e
commit
24c66d6435
7 changed files with 427 additions and 380 deletions
118
webapp/src/components/table/table.scss
Normal file
118
webapp/src/components/table/table.scss
Normal file
|
@ -0,0 +1,118 @@
|
|||
.Table {
|
||||
.octo-table-cell {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
color: rgb(var(--body-color));
|
||||
border-right: solid 1px rgba(var(--body-color), 0.09);
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
min-height: 32px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(var(--body-color), 0.05);
|
||||
}
|
||||
|
||||
&.title-cell {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.octo-icontitle {
|
||||
flex: 1 1 auto;
|
||||
|
||||
.octo-icon {
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
.Editable {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.header-cell {
|
||||
padding-right: 0;
|
||||
|
||||
.Icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
background-color: rgba(46, 170, 220, 0.15);
|
||||
border: 1px solid rgba(46, 170, 220, 0.6);
|
||||
}
|
||||
|
||||
.octo-editable {
|
||||
padding: 0 5px;
|
||||
display: relative;
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
.octo-editable.octo-editable.active {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.octo-propertyvalue {
|
||||
line-height: 17px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.octo-editable,
|
||||
.octo-propertyvalue {
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.octo-table-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.octo-table-header,
|
||||
.octo-table-row,
|
||||
.octo-table-footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
border-bottom: solid 1px rgba(var(--body-color), 0.09);
|
||||
}
|
||||
|
||||
.octo-table-header {
|
||||
.octo-table-cell {
|
||||
color: rgba(var(--body-color), 0.6);
|
||||
|
||||
.MenuWrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.octo-label {
|
||||
color: rgba(var(--body-color), 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.octo-table-footer {
|
||||
.octo-table-cell {
|
||||
color: rgba(var(--body-color), 0.6);
|
||||
cursor: pointer;
|
||||
|
||||
width: 100%;
|
||||
border-right: none;
|
||||
padding-left: 15px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(var(--body-color), 0.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
283
webapp/src/components/table/table.tsx
Normal file
283
webapp/src/components/table/table.tsx
Normal file
|
@ -0,0 +1,283 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
import {FormattedMessage, injectIntl} from 'react-intl'
|
||||
|
||||
import {IPropertyTemplate} from '../../blocks/board'
|
||||
import {MutableBoardView} from '../../blocks/boardView'
|
||||
import {Constants} from '../../constants'
|
||||
import mutator from '../../mutator'
|
||||
import {Utils} from '../../utils'
|
||||
import {BoardTree} from '../../viewModel/boardTree'
|
||||
import SortDownIcon from '../../widgets/icons/sortDown'
|
||||
import SortUpIcon from '../../widgets/icons/sortUp'
|
||||
import MenuWrapper from '../../widgets/menuWrapper'
|
||||
|
||||
import {HorizontalGrip} from '../horizontalGrip'
|
||||
|
||||
import './table.scss'
|
||||
import TableHeaderMenu from './tableHeaderMenu'
|
||||
import TableRow from './tableRow'
|
||||
|
||||
type Props = {
|
||||
boardTree: BoardTree
|
||||
readonly: boolean
|
||||
cardIdToFocusOnRender: string
|
||||
showCard: (cardId?: string) => void
|
||||
addCard: (show?: boolean) => Promise<void>
|
||||
}
|
||||
|
||||
type State = {
|
||||
shownCardId?: string
|
||||
}
|
||||
|
||||
class Table extends React.Component<Props, State> {
|
||||
private draggedHeaderTemplate?: IPropertyTemplate
|
||||
private cardIdToRowMap = new Map<string, React.RefObject<TableRow>>()
|
||||
state: State = {}
|
||||
|
||||
shouldComponentUpdate(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const {boardTree} = this.props
|
||||
const {board, cards, activeView} = boardTree
|
||||
const titleRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
let titleSortIcon: React.ReactNode
|
||||
const titleSortOption = activeView.sortOptions.find((o) => o.propertyId === Constants.titleColumnId)
|
||||
if (titleSortOption) {
|
||||
titleSortIcon = titleSortOption.reversed ? <SortUpIcon/> : <SortDownIcon/>
|
||||
}
|
||||
|
||||
this.cardIdToRowMap.clear()
|
||||
|
||||
return (
|
||||
<div className='octo-table-body Table'>
|
||||
|
||||
{/* Headers */}
|
||||
|
||||
<div
|
||||
className='octo-table-header'
|
||||
id='mainBoardHeader'
|
||||
>
|
||||
<div
|
||||
id='mainBoardHeader'
|
||||
ref={titleRef}
|
||||
className='octo-table-cell header-cell'
|
||||
style={{overflow: 'unset', width: this.columnWidth(Constants.titleColumnId)}}
|
||||
>
|
||||
<MenuWrapper disabled={this.props.readonly}>
|
||||
<div
|
||||
className='octo-label'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='TableComponent.name'
|
||||
defaultMessage='Name'
|
||||
/>
|
||||
{titleSortIcon}
|
||||
</div>
|
||||
<TableHeaderMenu
|
||||
boardTree={boardTree}
|
||||
templateId={Constants.titleColumnId}
|
||||
/>
|
||||
</MenuWrapper>
|
||||
|
||||
<div className='octo-spacer'/>
|
||||
|
||||
{!this.props.readonly &&
|
||||
<HorizontalGrip
|
||||
onDrag={(offset) => {
|
||||
const originalWidth = this.columnWidth(Constants.titleColumnId)
|
||||
const newWidth = Math.max(Constants.minColumnWidth, originalWidth + offset)
|
||||
if (titleRef.current) {
|
||||
titleRef.current.style.width = `${newWidth}px`
|
||||
}
|
||||
}}
|
||||
onDragEnd={(offset) => {
|
||||
Utils.log(`onDragEnd offset: ${offset}`)
|
||||
const originalWidth = this.columnWidth(Constants.titleColumnId)
|
||||
const newWidth = Math.max(Constants.minColumnWidth, originalWidth + offset)
|
||||
if (titleRef.current) {
|
||||
titleRef.current.style.width = `${newWidth}px`
|
||||
}
|
||||
|
||||
const columnWidths = {...activeView.columnWidths}
|
||||
if (newWidth !== columnWidths[Constants.titleColumnId]) {
|
||||
columnWidths[Constants.titleColumnId] = newWidth
|
||||
|
||||
const newView = new MutableBoardView(activeView)
|
||||
newView.columnWidths = columnWidths
|
||||
mutator.updateBlock(newView, activeView, 'resize column')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Table header row */}
|
||||
|
||||
{board.cardProperties.
|
||||
filter((template) => activeView.visiblePropertyIds.includes(template.id)).
|
||||
map((template) => {
|
||||
const headerRef = React.createRef<HTMLDivElement>()
|
||||
let sortIcon
|
||||
const sortOption = activeView.sortOptions.find((o) => o.propertyId === template.id)
|
||||
if (sortOption) {
|
||||
sortIcon = sortOption.reversed ? <SortUpIcon/> : <SortDownIcon/>
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={template.id}
|
||||
ref={headerRef}
|
||||
style={{overflow: 'unset', width: this.columnWidth(template.id)}}
|
||||
className='octo-table-cell header-cell'
|
||||
|
||||
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)
|
||||
}}
|
||||
>
|
||||
<MenuWrapper
|
||||
disabled={this.props.readonly}
|
||||
>
|
||||
<div
|
||||
className='octo-label'
|
||||
draggable={!this.props.readonly}
|
||||
onDragStart={() => {
|
||||
this.draggedHeaderTemplate = template
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
this.draggedHeaderTemplate = undefined
|
||||
}}
|
||||
>
|
||||
{template.name}
|
||||
{sortIcon}
|
||||
</div>
|
||||
<TableHeaderMenu
|
||||
boardTree={boardTree}
|
||||
templateId={template.id}
|
||||
/>
|
||||
</MenuWrapper>
|
||||
|
||||
<div className='octo-spacer'/>
|
||||
|
||||
{!this.props.readonly &&
|
||||
<HorizontalGrip
|
||||
onDrag={(offset) => {
|
||||
const originalWidth = this.columnWidth(template.id)
|
||||
const newWidth = Math.max(Constants.minColumnWidth, originalWidth + offset)
|
||||
if (headerRef.current) {
|
||||
headerRef.current.style.width = `${newWidth}px`
|
||||
}
|
||||
}}
|
||||
onDragEnd={(offset) => {
|
||||
Utils.log(`onDragEnd offset: ${offset}`)
|
||||
const originalWidth = this.columnWidth(template.id)
|
||||
const newWidth = Math.max(Constants.minColumnWidth, originalWidth + offset)
|
||||
if (headerRef.current) {
|
||||
headerRef.current.style.width = `${newWidth}px`
|
||||
}
|
||||
|
||||
const columnWidths = {...activeView.columnWidths}
|
||||
if (newWidth !== columnWidths[template.id]) {
|
||||
columnWidths[template.id] = newWidth
|
||||
|
||||
const newView = new MutableBoardView(activeView)
|
||||
newView.columnWidths = columnWidths
|
||||
mutator.updateBlock(newView, activeView, 'resize column')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Rows, one per card */}
|
||||
|
||||
{cards.map((card) => {
|
||||
const tableRowRef = React.createRef<TableRow>()
|
||||
|
||||
const tableRow = (
|
||||
<TableRow
|
||||
key={card.id + card.updateAt}
|
||||
ref={tableRowRef}
|
||||
boardTree={boardTree}
|
||||
card={card}
|
||||
focusOnMount={this.props.cardIdToFocusOnRender === card.id}
|
||||
onSaveWithEnter={() => {
|
||||
if (cards.length > 0 && cards[cards.length - 1] === card) {
|
||||
this.props.addCard(false)
|
||||
}
|
||||
}}
|
||||
showCard={this.props.showCard}
|
||||
readonly={this.props.readonly}
|
||||
/>)
|
||||
|
||||
this.cardIdToRowMap.set(card.id, tableRowRef)
|
||||
|
||||
return tableRow
|
||||
})}
|
||||
|
||||
{/* Add New row */}
|
||||
|
||||
<div className='octo-table-footer'>
|
||||
{!this.props.readonly &&
|
||||
<div
|
||||
className='octo-table-cell'
|
||||
onClick={() => {
|
||||
this.props.addCard()
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='TableComponent.plus-new'
|
||||
defaultMessage='+ New'
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private columnWidth(templateId: string): number {
|
||||
return Math.max(Constants.minColumnWidth, this.props.boardTree.activeView.columnWidths[templateId] || 0)
|
||||
}
|
||||
|
||||
private async onDropToColumn(template: IPropertyTemplate) {
|
||||
const {draggedHeaderTemplate} = this
|
||||
if (!draggedHeaderTemplate) {
|
||||
return
|
||||
}
|
||||
|
||||
const {boardTree} = this.props
|
||||
const {board} = boardTree
|
||||
|
||||
Utils.assertValue(mutator)
|
||||
Utils.assertValue(boardTree)
|
||||
|
||||
Utils.log(`ondrop. Source column: ${draggedHeaderTemplate.name}, dest column: ${template.name}`)
|
||||
|
||||
// Move template to new index
|
||||
const destIndex = template ? board.cardProperties.indexOf(template) : 0
|
||||
await mutator.changePropertyTemplateOrder(board, draggedHeaderTemplate, destIndex)
|
||||
}
|
||||
}
|
||||
|
||||
export default Table
|
|
@ -4,10 +4,10 @@
|
|||
import React, {FC} from 'react'
|
||||
import {injectIntl, IntlShape} from 'react-intl'
|
||||
|
||||
import {Constants} from '../constants'
|
||||
import mutator from '../mutator'
|
||||
import {BoardTree} from '../viewModel/boardTree'
|
||||
import Menu from '../widgets/menu'
|
||||
import {Constants} from '../../constants'
|
||||
import mutator from '../../mutator'
|
||||
import {BoardTree} from '../../viewModel/boardTree'
|
||||
import Menu from '../../widgets/menu'
|
||||
|
||||
type Props = {
|
||||
templateId: string
|
|
@ -3,14 +3,14 @@
|
|||
import React from 'react'
|
||||
import {FormattedMessage} from 'react-intl'
|
||||
|
||||
import {Card} from '../blocks/card'
|
||||
import {Constants} from '../constants'
|
||||
import mutator from '../mutator'
|
||||
import {BoardTree} from '../viewModel/boardTree'
|
||||
import Button from '../widgets/buttons/button'
|
||||
import Editable from '../widgets/editable'
|
||||
import {Card} from '../../blocks/card'
|
||||
import {Constants} from '../../constants'
|
||||
import mutator from '../../mutator'
|
||||
import {BoardTree} from '../../viewModel/boardTree'
|
||||
import Button from '../../widgets/buttons/button'
|
||||
import Editable from '../../widgets/editable'
|
||||
|
||||
import PropertyValueElement from './propertyValueElement'
|
||||
import PropertyValueElement from '../propertyValueElement'
|
||||
import './tableRow.scss'
|
||||
|
||||
type Props = {
|
||||
|
@ -122,4 +122,4 @@ class TableRow extends React.Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
export {TableRow}
|
||||
export default TableRow
|
|
@ -1,119 +1,2 @@
|
|||
.TableComponent {
|
||||
.octo-table-cell {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
color: rgb(var(--body-color));
|
||||
border-right: solid 1px rgba(var(--body-color), 0.09);
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
min-height: 32px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(var(--body-color), 0.05);
|
||||
}
|
||||
|
||||
&.title-cell {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.octo-icontitle {
|
||||
flex: 1 1 auto;
|
||||
|
||||
.octo-icon {
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
.Editable {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.header-cell {
|
||||
padding-right: 0;
|
||||
|
||||
.Icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
background-color: rgba(46, 170, 220, 0.15);
|
||||
border: 1px solid rgba(46, 170, 220, 0.6);
|
||||
}
|
||||
|
||||
.octo-editable {
|
||||
padding: 0 5px;
|
||||
display: relative;
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
.octo-editable.octo-editable.active {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.octo-propertyvalue {
|
||||
line-height: 17px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.octo-editable,
|
||||
.octo-propertyvalue {
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.octo-table-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.octo-table-header,
|
||||
.octo-table-row,
|
||||
.octo-table-footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
border-bottom: solid 1px rgba(var(--body-color), 0.09);
|
||||
}
|
||||
|
||||
.octo-table-header {
|
||||
.octo-table-cell {
|
||||
color: rgba(var(--body-color), 0.6);
|
||||
|
||||
.MenuWrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.octo-label {
|
||||
color: rgba(var(--body-color), 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.octo-table-footer {
|
||||
.octo-table-cell {
|
||||
color: rgba(var(--body-color), 0.6);
|
||||
cursor: pointer;
|
||||
|
||||
width: 100%;
|
||||
border-right: none;
|
||||
padding-left: 15px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(var(--body-color), 0.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,29 +1,22 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'
|
||||
import {injectIntl, IntlShape} from 'react-intl'
|
||||
|
||||
import {BlockIcons} from '../blockIcons'
|
||||
import {IPropertyTemplate} from '../blocks/board'
|
||||
import {MutableBoardView} from '../blocks/boardView'
|
||||
import {MutableCard} from '../blocks/card'
|
||||
import {Constants} from '../constants'
|
||||
import mutator from '../mutator'
|
||||
import {Utils} from '../utils'
|
||||
import {BoardTree} from '../viewModel/boardTree'
|
||||
import SortDownIcon from '../widgets/icons/sortDown'
|
||||
import SortUpIcon from '../widgets/icons/sortUp'
|
||||
import MenuWrapper from '../widgets/menuWrapper'
|
||||
|
||||
import CardDialog from './cardDialog'
|
||||
import {HorizontalGrip} from './horizontalGrip'
|
||||
import RootPortal from './rootPortal'
|
||||
import './tableComponent.scss'
|
||||
import TableHeaderMenu from './tableHeaderMenu'
|
||||
import {TableRow} from './tableRow'
|
||||
import TopBar from './topBar'
|
||||
import ViewHeader from './viewHeader'
|
||||
import ViewTitle from './viewTitle'
|
||||
import Table from './table/table'
|
||||
|
||||
type Props = {
|
||||
boardTree: BoardTree
|
||||
|
@ -35,13 +28,11 @@ type Props = {
|
|||
|
||||
type State = {
|
||||
shownCardId?: string
|
||||
cardIdToFocusOnRender: string
|
||||
}
|
||||
|
||||
class TableComponent extends React.Component<Props, State> {
|
||||
private draggedHeaderTemplate?: IPropertyTemplate
|
||||
private cardIdToRowMap = new Map<string, React.RefObject<TableRow>>()
|
||||
private cardIdToFocusOnRender?: string
|
||||
state: State = {}
|
||||
state: State = {cardIdToFocusOnRender: ''}
|
||||
|
||||
shouldComponentUpdate(): boolean {
|
||||
return true
|
||||
|
@ -61,16 +52,7 @@ class TableComponent extends React.Component<Props, State> {
|
|||
|
||||
render(): JSX.Element {
|
||||
const {boardTree, showView} = this.props
|
||||
const {board, cards, activeView} = boardTree
|
||||
const titleRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
let titleSortIcon
|
||||
const titleSortOption = activeView.sortOptions.find((o) => o.propertyId === Constants.titleColumnId)
|
||||
if (titleSortOption) {
|
||||
titleSortIcon = titleSortOption.reversed ? <SortUpIcon/> : <SortDownIcon/>
|
||||
}
|
||||
|
||||
this.cardIdToRowMap.clear()
|
||||
const {board} = boardTree
|
||||
|
||||
return (
|
||||
<div className='TableComponent octo-app'>
|
||||
|
@ -106,214 +88,13 @@ class TableComponent extends React.Component<Props, State> {
|
|||
/>
|
||||
|
||||
{/* Main content */}
|
||||
|
||||
<div className='octo-table-body'>
|
||||
|
||||
{/* Headers */}
|
||||
|
||||
<div
|
||||
className='octo-table-header'
|
||||
id='mainBoardHeader'
|
||||
>
|
||||
<div
|
||||
id='mainBoardHeader'
|
||||
ref={titleRef}
|
||||
className='octo-table-cell header-cell'
|
||||
style={{overflow: 'unset', width: this.columnWidth(Constants.titleColumnId)}}
|
||||
>
|
||||
<MenuWrapper
|
||||
disabled={this.props.readonly}
|
||||
>
|
||||
<div
|
||||
className='octo-label'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='TableComponent.name'
|
||||
defaultMessage='Name'
|
||||
/>
|
||||
{titleSortIcon}
|
||||
</div>
|
||||
<TableHeaderMenu
|
||||
boardTree={boardTree}
|
||||
templateId={Constants.titleColumnId}
|
||||
/>
|
||||
</MenuWrapper>
|
||||
|
||||
<div className='octo-spacer'/>
|
||||
|
||||
{!this.props.readonly &&
|
||||
<HorizontalGrip
|
||||
onDrag={(offset) => {
|
||||
const originalWidth = this.columnWidth(Constants.titleColumnId)
|
||||
const newWidth = Math.max(Constants.minColumnWidth, originalWidth + offset)
|
||||
if (titleRef.current) {
|
||||
titleRef.current.style.width = `${newWidth}px`
|
||||
}
|
||||
}}
|
||||
onDragEnd={(offset) => {
|
||||
Utils.log(`onDragEnd offset: ${offset}`)
|
||||
const originalWidth = this.columnWidth(Constants.titleColumnId)
|
||||
const newWidth = Math.max(Constants.minColumnWidth, originalWidth + offset)
|
||||
if (titleRef.current) {
|
||||
titleRef.current.style.width = `${newWidth}px`
|
||||
}
|
||||
|
||||
const columnWidths = {...activeView.columnWidths}
|
||||
if (newWidth !== columnWidths[Constants.titleColumnId]) {
|
||||
columnWidths[Constants.titleColumnId] = newWidth
|
||||
|
||||
const newView = new MutableBoardView(activeView)
|
||||
newView.columnWidths = columnWidths
|
||||
mutator.updateBlock(newView, activeView, 'resize column')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Table header row */}
|
||||
|
||||
{board.cardProperties.
|
||||
filter((template) => activeView.visiblePropertyIds.includes(template.id)).
|
||||
map((template) => {
|
||||
const headerRef = React.createRef<HTMLDivElement>()
|
||||
let sortIcon
|
||||
const sortOption = activeView.sortOptions.find((o) => o.propertyId === template.id)
|
||||
if (sortOption) {
|
||||
sortIcon = sortOption.reversed ? <SortUpIcon/> : <SortDownIcon/>
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={template.id}
|
||||
ref={headerRef}
|
||||
style={{overflow: 'unset', width: this.columnWidth(template.id)}}
|
||||
className='octo-table-cell header-cell'
|
||||
|
||||
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)
|
||||
}}
|
||||
>
|
||||
<MenuWrapper
|
||||
disabled={this.props.readonly}
|
||||
>
|
||||
<div
|
||||
className='octo-label'
|
||||
draggable={!this.props.readonly}
|
||||
onDragStart={() => {
|
||||
this.draggedHeaderTemplate = template
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
this.draggedHeaderTemplate = undefined
|
||||
}}
|
||||
>
|
||||
{template.name}
|
||||
{sortIcon}
|
||||
</div>
|
||||
<TableHeaderMenu
|
||||
boardTree={boardTree}
|
||||
templateId={template.id}
|
||||
/>
|
||||
</MenuWrapper>
|
||||
|
||||
<div className='octo-spacer'/>
|
||||
|
||||
{!this.props.readonly &&
|
||||
<HorizontalGrip
|
||||
onDrag={(offset) => {
|
||||
const originalWidth = this.columnWidth(template.id)
|
||||
const newWidth = Math.max(Constants.minColumnWidth, originalWidth + offset)
|
||||
if (headerRef.current) {
|
||||
headerRef.current.style.width = `${newWidth}px`
|
||||
}
|
||||
}}
|
||||
onDragEnd={(offset) => {
|
||||
Utils.log(`onDragEnd offset: ${offset}`)
|
||||
const originalWidth = this.columnWidth(template.id)
|
||||
const newWidth = Math.max(Constants.minColumnWidth, originalWidth + offset)
|
||||
if (headerRef.current) {
|
||||
headerRef.current.style.width = `${newWidth}px`
|
||||
}
|
||||
|
||||
const columnWidths = {...activeView.columnWidths}
|
||||
if (newWidth !== columnWidths[template.id]) {
|
||||
columnWidths[template.id] = newWidth
|
||||
|
||||
const newView = new MutableBoardView(activeView)
|
||||
newView.columnWidths = columnWidths
|
||||
mutator.updateBlock(newView, activeView, 'resize column')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Rows, one per card */}
|
||||
|
||||
{cards.map((card) => {
|
||||
const tableRowRef = React.createRef<TableRow>()
|
||||
|
||||
let focusOnMount = false
|
||||
if (this.cardIdToFocusOnRender && this.cardIdToFocusOnRender === card.id) {
|
||||
this.cardIdToFocusOnRender = undefined
|
||||
focusOnMount = true
|
||||
}
|
||||
|
||||
const tableRow = (
|
||||
<TableRow
|
||||
key={card.id + card.updateAt}
|
||||
ref={tableRowRef}
|
||||
boardTree={boardTree}
|
||||
card={card}
|
||||
focusOnMount={focusOnMount}
|
||||
onSaveWithEnter={() => {
|
||||
if (cards.length > 0 && cards[cards.length - 1] === card) {
|
||||
this.addCard(false)
|
||||
}
|
||||
}}
|
||||
showCard={this.showCard}
|
||||
readonly={this.props.readonly}
|
||||
/>)
|
||||
|
||||
this.cardIdToRowMap.set(card.id, tableRowRef)
|
||||
|
||||
return tableRow
|
||||
})}
|
||||
|
||||
{/* Add New row */}
|
||||
|
||||
<div className='octo-table-footer'>
|
||||
{!this.props.readonly &&
|
||||
<div
|
||||
className='octo-table-cell'
|
||||
onClick={() => {
|
||||
this.addCard()
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='TableComponent.plus-new'
|
||||
defaultMessage='+ New'
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
boardTree={boardTree}
|
||||
readonly={this.props.readonly}
|
||||
cardIdToFocusOnRender={this.state.cardIdToFocusOnRender}
|
||||
showCard={this.showCard}
|
||||
addCard={this.addCard}
|
||||
/>
|
||||
</div>
|
||||
</div >
|
||||
</div >
|
||||
|
@ -365,7 +146,8 @@ class TableComponent extends React.Component<Props, State> {
|
|||
this.showCard(card.id)
|
||||
} else {
|
||||
// Focus on this card's title inline on next render
|
||||
this.cardIdToFocusOnRender = card.id
|
||||
this.setState({cardIdToFocusOnRender: card.id})
|
||||
setTimeout(() => this.setState({cardIdToFocusOnRender: ''}), 100)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -392,25 +174,6 @@ class TableComponent extends React.Component<Props, State> {
|
|||
private editCardTemplate = (cardTemplateId: string) => {
|
||||
this.showCard(cardTemplateId)
|
||||
}
|
||||
|
||||
private async onDropToColumn(template: IPropertyTemplate) {
|
||||
const {draggedHeaderTemplate} = this
|
||||
if (!draggedHeaderTemplate) {
|
||||
return
|
||||
}
|
||||
|
||||
const {boardTree} = this.props
|
||||
const {board} = boardTree
|
||||
|
||||
Utils.assertValue(mutator)
|
||||
Utils.assertValue(boardTree)
|
||||
|
||||
Utils.log(`ondrop. Source column: ${draggedHeaderTemplate.name}, dest column: ${template.name}`)
|
||||
|
||||
// Move template to new index
|
||||
const destIndex = template ? board.cardProperties.indexOf(template) : 0
|
||||
await mutator.changePropertyTemplateOrder(board, draggedHeaderTemplate, destIndex)
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(TableComponent)
|
||||
|
|
Loading…
Reference in a new issue