Migrating cardDialog to functional component and creating the cardListener hook

This commit is contained in:
Jesús Espino 2021-03-30 15:15:19 +02:00
parent 361c37c1e0
commit 239fc689d4
2 changed files with 126 additions and 122 deletions

View file

@ -1,17 +1,17 @@
// 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 from 'react' import React, {useState} from 'react'
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl' import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'
import mutator from '../mutator' import mutator from '../mutator'
import octoClient from '../octoClient'
import {OctoListener} from '../octoListener'
import {Utils} from '../utils' import {Utils} from '../utils'
import {BoardTree} from '../viewModel/boardTree' import {BoardTree} from '../viewModel/boardTree'
import {CardTree, MutableCardTree} from '../viewModel/cardTree' import {CardTree, MutableCardTree} from '../viewModel/cardTree'
import DeleteIcon from '../widgets/icons/delete' import DeleteIcon from '../widgets/icons/delete'
import Menu from '../widgets/menu' import Menu from '../widgets/menu'
import useCardListener from '../hooks/cardListener'
import CardDetail from './cardDetail/cardDetail' import CardDetail from './cardDetail/cardDetail'
import Dialog from './dialog' import Dialog from './dialog'
@ -24,63 +24,44 @@ type Props = {
readonly: boolean readonly: boolean
} }
type State = { const CardDialog = (props: Props) => {
cardTree?: CardTree, const [syncComplete, setSyncComplete] = useState(false)
syncComplete: boolean const [cardTree, setCardTree] = useState<CardTree>()
} useCardListener(
props.cardId,
class CardDialog extends React.Component<Props, State> {
state: State = {syncComplete: false}
private cardListener?: OctoListener
shouldComponentUpdate(): boolean {
return true
}
componentDidMount(): void {
this.createCardTreeAndSync()
}
private async createCardTreeAndSync() {
const cardTree = await MutableCardTree.sync(this.props.cardId)
this.createListener()
this.setState({cardTree, syncComplete: true})
Utils.log(`cardDialog.createCardTreeAndSync: ${cardTree?.card.id}`)
}
private createListener() {
this.deleteListener()
this.cardListener = new OctoListener()
this.cardListener.open(
octoClient.workspaceId,
[this.props.cardId],
async (blocks) => { async (blocks) => {
Utils.log(`cardListener.onChanged: ${blocks.length}`) Utils.log(`cardListener.onChanged: ${blocks.length}`)
const newCardTree = this.state.cardTree ? MutableCardTree.incrementalUpdate(this.state.cardTree, blocks) : await MutableCardTree.sync(this.props.cardId) const newCardTree = cardTree ? MutableCardTree.incrementalUpdate(cardTree, blocks) : await MutableCardTree.sync(props.cardId)
this.setState({cardTree: newCardTree, syncComplete: true}) setCardTree(newCardTree)
setSyncComplete(true)
}, },
async () => { async () => {
Utils.log('cardListener.onReconnect') Utils.log('cardListener.onReconnect')
const newCardTree = await MutableCardTree.sync(this.props.cardId) const newCardTree = await MutableCardTree.sync(props.cardId)
this.setState({cardTree: newCardTree, syncComplete: true}) setCardTree(newCardTree)
setSyncComplete(true)
},
)
const makeTemplateClicked = async () => {
if (!cardTree) {
Utils.assertFailure('cardTree')
return
}
await mutator.duplicateCard(
cardTree.card.id,
props.intl.formatMessage({id: 'Mutator.new-template-from-card', defaultMessage: 'new template from card'}),
true,
async (newCardId) => {
props.showCard(newCardId)
},
async () => {
props.showCard(undefined)
}, },
) )
} }
private deleteListener() {
this.cardListener?.close()
this.cardListener = undefined
}
componentWillUnmount(): void {
this.deleteListener()
}
render(): JSX.Element {
const {cardTree} = this.state
const menu = ( const menu = (
<Menu position='left'> <Menu position='left'>
<Menu.Text <Menu.Text
@ -88,28 +69,28 @@ class CardDialog extends React.Component<Props, State> {
icon={<DeleteIcon/>} icon={<DeleteIcon/>}
name='Delete' name='Delete'
onClick={async () => { onClick={async () => {
const card = this.state.cardTree?.card const card = cardTree?.card
if (!card) { if (!card) {
Utils.assertFailure() Utils.assertFailure()
return return
} }
await mutator.deleteBlock(card, 'delete card') await mutator.deleteBlock(card, 'delete card')
this.props.onClose() props.onClose()
}} }}
/> />
{(cardTree && !cardTree.card.isTemplate) && {(cardTree && !cardTree.card.isTemplate) &&
<Menu.Text <Menu.Text
id='makeTemplate' id='makeTemplate'
name='New template from card' name='New template from card'
onClick={this.makeTemplateClicked} onClick={makeTemplateClicked}
/> />
} }
</Menu> </Menu>
) )
return ( return (
<Dialog <Dialog
onClose={this.props.onClose} onClose={props.onClose}
toolsMenu={!this.props.readonly && menu} toolsMenu={!props.readonly && menu}
> >
{(cardTree?.card.isTemplate) && {(cardTree?.card.isTemplate) &&
<div className='banner'> <div className='banner'>
@ -119,14 +100,14 @@ class CardDialog extends React.Component<Props, State> {
/> />
</div> </div>
} }
{this.state.cardTree && {cardTree &&
<CardDetail <CardDetail
boardTree={this.props.boardTree} boardTree={props.boardTree}
cardTree={this.state.cardTree} cardTree={cardTree}
readonly={this.props.readonly} readonly={props.readonly}
/> />
} }
{(!this.state.cardTree && this.state.syncComplete) && {(!cardTree && syncComplete) &&
<div className='banner error'> <div className='banner error'>
<FormattedMessage <FormattedMessage
id='CardDialog.nocard' id='CardDialog.nocard'
@ -138,25 +119,4 @@ class CardDialog extends React.Component<Props, State> {
) )
} }
private makeTemplateClicked = async () => {
const {cardTree} = this.state
if (!cardTree) {
Utils.assertFailure('this.state.cardTree')
return
}
await mutator.duplicateCard(
cardTree.card.id,
this.props.intl.formatMessage({id: 'Mutator.new-template-from-card', defaultMessage: 'new template from card'}),
true,
async (newCardId) => {
this.props.showCard(newCardId)
},
async () => {
this.props.showCard(undefined)
},
)
}
}
export default injectIntl(CardDialog) export default injectIntl(CardDialog)

View file

@ -0,0 +1,44 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useEffect} from 'react'
import octoClient from '../octoClient'
import {OctoListener} from '../octoListener'
import {MutableCardTree} from '../viewModel/cardTree'
import {Utils} from '../utils'
import {IBlock} from '../blocks/block'
export default function useCardListener(cardId:string, onChange: (blocks: IBlock[]) => void, onReconnect: () => void): void {
let cardListener: OctoListener | null = null
const deleteListener = () => {
cardListener?.close()
cardListener = null
}
const createListener = () => {
deleteListener()
cardListener = new OctoListener()
cardListener.open(
octoClient.workspaceId,
[cardId],
onChange,
onReconnect,
)
}
const createCardTreeAndSync = async () => {
onReconnect()
createListener()
}
useEffect(() => {
Utils.log(`useCardListener.connect: ${cardId}`)
createCardTreeAndSync()
return () => {
Utils.log(`useCardListener.disconnect: ${cardId}`)
deleteListener()
}
}, [cardId])
}