Merge pull request #21 from mattermost/resize-columns

Resize table columns
This commit is contained in:
Jesús Espino 2020-11-03 07:57:19 +01:00 committed by GitHub
commit e55e5fa2a4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 179 additions and 28 deletions

View file

@ -17,6 +17,7 @@ interface BoardView extends IBlock {
readonly hiddenOptionIds: readonly string[]
readonly filter: FilterGroup | undefined
readonly cardOrder: readonly string[]
readonly columnWidths: Readonly<Record<string, number>>
}
class MutableBoardView extends MutableBlock {
@ -76,6 +77,13 @@ class MutableBoardView extends MutableBlock {
this.fields.cardOrder = value
}
get columnWidths(): Record<string, number> {
return this.fields.columnWidths as Record<string, number>
}
set columnWidths(value: Record<string, number>) {
this.fields.columnWidths = value
}
constructor(block: any = {}) {
super(block)
@ -87,6 +95,7 @@ class MutableBoardView extends MutableBlock {
this.hiddenOptionIds = block.fields?.hiddenOptionIds?.slice() || []
this.filter = new FilterGroup(block.fields?.filter)
this.cardOrder = block.fields?.cardOrder?.slice() || []
this.columnWidths = {...(block.fields?.columnWidths || {})}
if (!this.viewType) {
this.viewType = 'board'

View file

@ -0,0 +1,8 @@
.HorizontalGrip {
width: 5px;
cursor: ew-resize;
&:hover {
background-color: rgba(90, 192, 255, 0.7);
}
}

View file

@ -0,0 +1,54 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
import './horizontalGrip.scss'
type Props = {
onDrag: (offset: number) => void
onDragEnd: (offset: number) => void
}
type State = {
isDragging?: boolean
startX?: number
offset?: number
}
class HorizontalGrip extends React.PureComponent<Props, State> {
state: State = {
}
render(): JSX.Element {
return (
<div
className='HorizontalGrip'
onMouseDown={(e) => {
this.setState({isDragging: true, startX: e.clientX, offset: 0})
window.addEventListener('mousemove', this.globalMouseMove)
window.addEventListener('mouseup', this.globalMouseUp)
}}
/>)
}
private globalMouseMove = (e: MouseEvent) => {
if (!this.state.isDragging) {
return
}
const offset = e.clientX - this.state.startX
if (offset !== this.state.offset) {
this.props.onDrag(offset)
this.setState({offset})
}
}
private globalMouseUp = (e: MouseEvent) => {
window.removeEventListener('mousemove', this.globalMouseMove)
window.removeEventListener('mouseup', this.globalMouseUp)
this.setState({isDragging: false})
const offset = e.clientX - this.state.startX
this.props.onDragEnd(offset)
}
}
export {HorizontalGrip}

View file

@ -9,7 +9,6 @@
box-sizing: border-box;
padding: 5px 8px 6px 8px;
width: 150px;
min-height: 32px;
font-size: 14px;
@ -18,13 +17,18 @@
position: relative;
.octo-icontitle {
flex: 1 1 auto;
.octo-icon {
min-width: 20px;
}
.Editable {
flex: 1 1 auto;
}
}
&.title-cell {
width: 280px;
&.header-cell {
padding-right: 0;
}
&:focus-within {

View file

@ -3,6 +3,7 @@
import React from 'react'
import {FormattedMessage} from 'react-intl'
import {Constants} from '../constants'
import {BlockIcons} from '../blockIcons'
import {IPropertyTemplate} from '../blocks/board'
import {Card, MutableCard} from '../blocks/card'
@ -13,7 +14,6 @@ import {Utils} from '../utils'
import MenuWrapper from '../widgets/menuWrapper'
import {CardDialog} from './cardDialog'
import {Editable} from './editable'
import RootPortal from './rootPortal'
import {TableRow} from './tableRow'
import ViewHeader from './viewHeader'
@ -21,6 +21,9 @@ import ViewTitle from './viewTitle'
import TableHeaderMenu from './tableHeaderMenu'
import './tableComponent.scss'
import {HorizontalGrip} from './horizontalGrip'
import {MutableBoardView} from '../blocks/boardView'
type Props = {
boardTree?: BoardTree
@ -57,6 +60,7 @@ class TableComponent extends React.Component<Props, State> {
}
const {board, cards, activeView} = boardTree
const titleRef = React.createRef<HTMLDivElement>()
this.cardIdToRowMap.clear()
@ -95,9 +99,10 @@ class TableComponent extends React.Component<Props, State> {
id='mainBoardHeader'
>
<div
className='octo-table-cell title-cell'
style={{overflow: 'unset'}}
id='mainBoardHeader'
ref={titleRef}
className='octo-table-cell title-cell header-cell'
style={{overflow: 'unset', width: this.columnWidth(Constants.titleColumnId)}}
>
<MenuWrapper>
<div
@ -114,23 +119,44 @@ class TableComponent extends React.Component<Props, State> {
templateId='__name'
/>
</MenuWrapper>
<div className='octo-spacer'/>
<HorizontalGrip
onDrag={(offset) => {
const originalWidth = this.columnWidth(Constants.titleColumnId)
const newWidth = Math.max(Constants.minColumnWidth, originalWidth + offset)
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)
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) =>
(<div
map((template) => {
const headerRef = React.createRef<HTMLDivElement>()
return (<div
key={template.id}
style={{overflow: 'unset'}}
className='octo-table-cell'
draggable={true}
onDragStart={() => {
this.draggedHeaderTemplate = template
}}
onDragEnd={() => {
this.draggedHeaderTemplate = undefined
}}
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')
@ -149,14 +175,46 @@ class TableComponent extends React.Component<Props, State> {
<div
className='octo-label'
style={{cursor: 'pointer'}}
draggable={true}
onDragStart={() => {
this.draggedHeaderTemplate = template
}}
onDragEnd={() => {
this.draggedHeaderTemplate = undefined
}}
>{template.name}</div>
<TableHeaderMenu
boardTree={boardTree}
templateId={template.id}
/>
</MenuWrapper>
</div>),
)}
<div className='octo-spacer'/>
<HorizontalGrip
onDrag={(offset) => {
const originalWidth = this.columnWidth(template.id)
const newWidth = Math.max(Constants.minColumnWidth, originalWidth + offset)
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)
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 */}
@ -213,6 +271,10 @@ class TableComponent extends React.Component<Props, State> {
)
}
private columnWidth(templateId: string): number {
return Math.max(Constants.minColumnWidth, this.props.boardTree?.activeView?.columnWidths[templateId] || 0)
}
private addCard = async (show = false) => {
const {boardTree} = this.props

View file

@ -7,6 +7,7 @@ import {BoardTree} from '../viewModel/boardTree'
import {Card} from '../blocks/card'
import mutator from '../mutator'
import {Constants} from '../constants'
import Editable from '../widgets/editable'
import Button from '../widgets/buttons/button'
@ -63,6 +64,7 @@ class TableRow extends React.Component<Props, State> {
<div
className='octo-table-cell title-cell'
id='mainBoardHeader'
style={{width: this.columnWidth(Constants.titleColumnId)}}
>
<div className='octo-icontitle'>
<div className='octo-icon'>{card.icon}</div>
@ -108,6 +110,7 @@ class TableRow extends React.Component<Props, State> {
<div
className='octo-table-cell'
key={template.id}
style={{width: this.columnWidth(template.id)}}
>
<PropertyValueElement
readOnly={false}
@ -122,6 +125,10 @@ class TableRow extends React.Component<Props, State> {
)
}
private columnWidth(templateId: string): number {
return Math.max(Constants.minColumnWidth, this.props.boardTree?.activeView?.columnWidths[templateId] || 0)
}
focusOnTitle(): void {
this.titleRef.current?.focus()
}

View file

@ -8,6 +8,7 @@ import {BoardTree} from '../viewModel/boardTree'
import mutator from '../mutator'
import {Utils} from '../utils'
import Menu from '../widgets/menu'
import { Constants } from '../constants'
type Props = {
boardTree?: BoardTree
@ -62,6 +63,8 @@ export default class ViewMenu extends React.Component<Props> {
view.viewType = 'table'
view.parentId = board.id
view.visiblePropertyIds = board.cardProperties.map((o) => o.id)
view.columnWidths = {}
view.columnWidths[Constants.titleColumnId] = Constants.defaultTitleColumnWidth
const oldViewId = boardTree.activeView.id

View file

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
class Constants {
static menuColors = [
static readonly menuColors = [
{id: 'propColorDefault', name: 'Default', type: 'color'},
{id: 'propColorGray', name: 'Gray', type: 'color'},
{id: 'propColorBrown', name: 'Brown', type: 'color'},
@ -14,6 +14,10 @@ class Constants {
{id: 'propColorPink', name: 'Pink', type: 'color'},
{id: 'propColorRed', name: 'Red', type: 'color'},
]
static readonly minColumnWidth = 100
static readonly defaultTitleColumnWidth = 280
static readonly titleColumnId = '__title'
}
export {Constants}

View file

@ -40,7 +40,7 @@ class Mutator {
const groupId = this.beginUndoGroup()
try {
await actions()
} catch(err) {
} catch (err) {
Utils.assertFailure(`ERROR: ${err?.toString?.()}`)
}
this.endUndoGroup(groupId)
@ -55,7 +55,7 @@ class Mutator {
await octoClient.updateBlock(oldBlock)
},
description,
this.undoGroupId
this.undoGroupId,
)
}
@ -68,7 +68,7 @@ class Mutator {
await octoClient.updateBlocks(oldBlocks)
},
description,
this.undoGroupId
this.undoGroupId,
)
}
@ -83,7 +83,7 @@ class Mutator {
await octoClient.deleteBlock(block.id)
},
description,
this.undoGroupId
this.undoGroupId,
)
}
@ -100,7 +100,7 @@ class Mutator {
}
},
description,
this.undoGroupId
this.undoGroupId,
)
}
@ -119,7 +119,7 @@ class Mutator {
await afterUndo?.()
},
description,
this.undoGroupId
this.undoGroupId,
)
}
@ -491,7 +491,7 @@ class Mutator {
await octoClient.deleteBlock(block.id)
},
'add image',
this.undoGroupId
this.undoGroupId,
)
return block