focalboard/webapp/src/components/cardDetail.tsx

258 lines
10 KiB
TypeScript
Raw Normal View History

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'
import {FormattedMessage, IntlShape, injectIntl} from 'react-intl'
2020-10-21 03:28:55 +02:00
import {BlockIcons} from '../blockIcons'
import {MutableTextBlock} from '../blocks/textBlock'
import {BoardTree} from '../viewModel/boardTree'
import {CardTree, MutableCardTree} from '../viewModel/cardTree'
import mutator from '../mutator'
import {OctoListener} from '../octoListener'
import {Utils} from '../utils'
2020-10-21 03:28:55 +02:00
import MenuWrapper from '../widgets/menuWrapper'
import Menu from '../widgets/menu'
import Editable from '../widgets/editable'
import Button from './button'
import {MarkdownEditor} from './markdownEditor'
2020-10-25 15:36:49 +01:00
import ContentBlock from './contentBlock'
import CommentsList from './commentsList'
import PropertyMenu from './propertyMenu'
2020-10-26 12:43:16 +01:00
import PropertyValueElement from './propertyValueElement'
import './cardDetail.scss'
type Props = {
2020-10-20 21:50:53 +02:00
boardTree: BoardTree
2020-10-21 03:28:55 +02:00
cardId: string
intl: IntlShape
}
type State = {
2020-10-20 21:50:53 +02:00
cardTree?: CardTree
title: string
}
class CardDetail extends React.Component<Props, State> {
2020-10-20 21:50:53 +02:00
private titleRef = React.createRef<Editable>()
2020-10-21 18:32:36 +02:00
private cardListener?: OctoListener
2020-10-20 21:50:53 +02:00
2020-10-25 14:40:47 +01:00
shouldComponentUpdate() {
return true
}
2020-10-20 21:50:53 +02:00
constructor(props: Props) {
super(props)
this.state = {
title: '',
}
2020-10-20 21:50:53 +02:00
}
componentDidMount() {
this.cardListener = new OctoListener()
this.cardListener.open([this.props.cardId], async (blockId) => {
2020-10-21 03:28:55 +02:00
Utils.log(`cardListener.onChanged: ${blockId}`)
2020-10-20 21:50:53 +02:00
await cardTree.sync()
this.setState({cardTree})
2020-10-20 21:52:56 +02:00
})
const cardTree = new MutableCardTree(this.props.cardId)
2020-10-20 21:50:53 +02:00
cardTree.sync().then(() => {
this.setState({cardTree, title: cardTree.card.title})
setTimeout(() => {
2020-10-20 21:50:53 +02:00
if (this.titleRef.current) {
this.titleRef.current.focus()
}
}, 0)
2020-10-20 21:52:56 +02:00
})
2020-10-20 21:50:53 +02:00
}
2020-10-21 18:32:36 +02:00
componentWillUnmount() {
this.cardListener?.close()
this.cardListener = undefined
}
2020-10-20 21:50:53 +02:00
render() {
const {boardTree, intl} = this.props
2020-10-21 03:28:55 +02:00
const {cardTree} = this.state
2020-10-20 21:50:53 +02:00
const {board} = boardTree
if (!cardTree) {
return null
2020-10-20 21:50:53 +02:00
}
2020-10-21 03:28:55 +02:00
const {card, comments} = cardTree
2020-10-20 21:50:53 +02:00
let contentElements
if (cardTree.contents.length > 0) {
contentElements =
(<div className='octo-content'>
2020-10-25 15:36:49 +01:00
{cardTree.contents.map((block) => (
<ContentBlock
key={block.id}
block={block}
cardId={card.id}
cardTree={cardTree}
/>
))}
2020-10-20 21:50:53 +02:00
</div>)
} else {
2020-10-20 21:50:53 +02:00
contentElements = (<div className='octo-content'>
<div className='octo-block octo-hover-container'>
<div className='octo-block-margin'/>
<MarkdownEditor
text=''
placeholderText='Add a description...'
onChanged={(text) => {
2020-10-21 03:28:55 +02:00
const block = new MutableTextBlock()
block.parentId = card.id
block.title = text
block.order = cardTree.contents.length * 1000
2020-10-20 21:50:53 +02:00
mutator.insertBlock(block, 'add card text')
}}
/>
</div>
</div>)
}
2020-10-20 21:50:53 +02:00
const icon = card.icon
return (
2020-10-20 21:50:53 +02:00
<>
<div className='CardDetail content'>
{icon &&
<MenuWrapper>
<div className='octo-button octo-icon octo-card-icon'>{icon}</div>
<Menu>
<Menu.Text
id='random'
name={intl.formatMessage({id: 'CardDetail.random-icon', defaultMessage: 'Random'})}
onClick={() => mutator.changeIcon(card, BlockIcons.shared.randomIcon())}
/>
<Menu.Text
id='remove'
name={intl.formatMessage({id: 'CardDetail.remove-icon', defaultMessage: 'Remove Icon'})}
onClick={() => mutator.changeIcon(card, undefined, 'remove icon')}
/>
</Menu>
</MenuWrapper>}
{!icon &&
<div className='octo-hovercontrols'>
<Button
onClick={() => {
const newIcon = BlockIcons.shared.randomIcon()
mutator.changeIcon(card, newIcon)
2020-10-27 22:00:15 +01:00
}}
>
<FormattedMessage
id='CardDetail.add-icon'
defaultMessage='Add Icon'
/>
</Button>
</div>}
2020-10-20 21:50:53 +02:00
<Editable
ref={this.titleRef}
className='title'
value={this.state.title}
placeholderText='Untitled'
onChange={(title: string) => this.setState({title})}
2020-10-26 20:30:32 +01:00
onSave={() => mutator.changeTitle(card, this.state.title)}
onCancel={() => this.setState({title: this.state.cardTree.card.title})}
/>
2020-10-20 21:50:53 +02:00
{/* Property list */}
<div className='octo-propertylist'>
{board.cardProperties.map((propertyTemplate) => {
2020-10-20 21:50:53 +02:00
return (
<div
key={propertyTemplate.id}
className='octo-propertyrow'
>
<MenuWrapper>
<div className='octo-button octo-propertyname'>{propertyTemplate.name}</div>
<PropertyMenu
property={propertyTemplate}
boardTree={boardTree}
/>
</MenuWrapper>
2020-10-26 12:43:16 +01:00
<PropertyValueElement
readOnly={false}
card={card}
propertyTemplate={propertyTemplate}
emptyDisplayValue='Empty'
/>
2020-10-20 21:50:53 +02:00
</div>
)
2020-10-20 21:50:53 +02:00
})}
<div
2020-10-26 07:39:20 +01:00
className='octo-button octo-propertyname add-property'
2020-10-20 21:50:53 +02:00
onClick={async () => {
// TODO: Show UI
await mutator.insertPropertyTemplate(boardTree)
}}
2020-10-26 07:39:20 +01:00
>
<FormattedMessage
id='CardDetail.add-property'
defaultMessage='+ Add a property'
/>
</div>
</div>
2020-10-20 21:50:53 +02:00
{/* Comments */}
<hr/>
2020-10-27 22:00:15 +01:00
<CommentsList
comments={comments}
cardId={card.id}
/>
2020-10-20 21:50:53 +02:00
<hr/>
</div>
{/* Content blocks */}
2020-10-20 21:50:53 +02:00
<div className='CardDetail content fullwidth'>
2020-10-20 21:50:53 +02:00
{contentElements}
</div>
<div className='CardDetail content'>
2020-10-20 21:50:53 +02:00
<div className='octo-hoverpanel octo-hover-container'>
2020-10-25 14:40:47 +01:00
<MenuWrapper>
<div className='octo-button octo-hovercontrol octo-hover-item'>
<FormattedMessage
id='CardDetail.add-content'
defaultMessage='Add content'
/>
</div>
<Menu>
<Menu.Text
id='text'
name={intl.formatMessage({id: 'CardDetail.text', defaultMessage: 'Text'})}
onClick={() => {
2020-10-21 03:28:55 +02:00
const block = new MutableTextBlock()
block.parentId = card.id
block.order = cardTree.contents.length * 1000
2020-10-25 14:40:47 +01:00
mutator.insertBlock(block, 'add text')
}}
/>
<Menu.Text
id='image'
name={intl.formatMessage({id: 'CardDetail.image', defaultMessage: 'Image'})}
onClick={() => Utils.selectLocalFile(
(file) => mutator.createImageBlock(card.id, file, cardTree.contents.length * 1000),
'.jpg,.jpeg,.png',
)}
/>
</Menu>
</MenuWrapper>
</div>
2020-10-20 21:50:53 +02:00
</div>
</>
2020-10-20 21:50:53 +02:00
)
}
}
export default injectIntl(CardDetail)