diff --git a/webapp/src/components/cardDialog.tsx b/webapp/src/components/cardDialog.tsx index 78de4f7db..91f524062 100644 --- a/webapp/src/components/cardDialog.tsx +++ b/webapp/src/components/cardDialog.tsx @@ -1,17 +1,17 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react' +import React, {useState} from 'react' import {FormattedMessage, injectIntl, IntlShape} from 'react-intl' import mutator from '../mutator' -import octoClient from '../octoClient' -import {OctoListener} from '../octoListener' import {Utils} from '../utils' import {BoardTree} from '../viewModel/boardTree' import {CardTree, MutableCardTree} from '../viewModel/cardTree' import DeleteIcon from '../widgets/icons/delete' import Menu from '../widgets/menu' +import useCardListener from '../hooks/cardListener' + import CardDetail from './cardDetail/cardDetail' import Dialog from './dialog' @@ -24,139 +24,99 @@ type Props = { readonly: boolean } -type State = { - cardTree?: CardTree, - syncComplete: boolean -} +const CardDialog = (props: Props) => { + const [syncComplete, setSyncComplete] = useState(false) + const [cardTree, setCardTree] = useState() + useCardListener( + props.cardId, + async (blocks) => { + Utils.log(`cardListener.onChanged: ${blocks.length}`) + const newCardTree = cardTree ? MutableCardTree.incrementalUpdate(cardTree, blocks) : await MutableCardTree.sync(props.cardId) + setCardTree(newCardTree) + setSyncComplete(true) + }, + async () => { + Utils.log('cardListener.onReconnect') + const newCardTree = await MutableCardTree.sync(props.cardId) + setCardTree(newCardTree) + setSyncComplete(true) + }, + ) -class CardDialog extends React.Component { - 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) => { - Utils.log(`cardListener.onChanged: ${blocks.length}`) - const newCardTree = this.state.cardTree ? MutableCardTree.incrementalUpdate(this.state.cardTree, blocks) : await MutableCardTree.sync(this.props.cardId) - this.setState({cardTree: newCardTree, syncComplete: true}) - }, - async () => { - Utils.log('cardListener.onReconnect') - const newCardTree = await MutableCardTree.sync(this.props.cardId) - this.setState({cardTree: newCardTree, syncComplete: true}) - }, - ) - } - - private deleteListener() { - this.cardListener?.close() - this.cardListener = undefined - } - - componentWillUnmount(): void { - this.deleteListener() - } - - render(): JSX.Element { - const {cardTree} = this.state - - const menu = ( - - } - name='Delete' - onClick={async () => { - const card = this.state.cardTree?.card - if (!card) { - Utils.assertFailure() - return - } - await mutator.deleteBlock(card, 'delete card') - this.props.onClose() - }} - /> - {(cardTree && !cardTree.card.isTemplate) && - - } - - ) - return ( - - {(cardTree?.card.isTemplate) && -
- -
- } - {this.state.cardTree && - - } - {(!this.state.cardTree && this.state.syncComplete) && -
- -
- } -
- ) - } - - private makeTemplateClicked = async () => { - const {cardTree} = this.state + const makeTemplateClicked = async () => { if (!cardTree) { - Utils.assertFailure('this.state.cardTree') + Utils.assertFailure('cardTree') return } await mutator.duplicateCard( cardTree.card.id, - this.props.intl.formatMessage({id: 'Mutator.new-template-from-card', defaultMessage: 'new template from card'}), + props.intl.formatMessage({id: 'Mutator.new-template-from-card', defaultMessage: 'new template from card'}), true, async (newCardId) => { - this.props.showCard(newCardId) + props.showCard(newCardId) }, async () => { - this.props.showCard(undefined) + props.showCard(undefined) }, ) } + + const menu = ( + + } + name='Delete' + onClick={async () => { + const card = cardTree?.card + if (!card) { + Utils.assertFailure() + return + } + await mutator.deleteBlock(card, 'delete card') + props.onClose() + }} + /> + {(cardTree && !cardTree.card.isTemplate) && + + } + + ) + return ( + + {(cardTree?.card.isTemplate) && +
+ +
+ } + {cardTree && + + } + {(!cardTree && syncComplete) && +
+ +
+ } +
+ ) } export default injectIntl(CardDialog) diff --git a/webapp/src/hooks/cardListener.tsx b/webapp/src/hooks/cardListener.tsx new file mode 100644 index 000000000..86772bd1c --- /dev/null +++ b/webapp/src/hooks/cardListener.tsx @@ -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]) +}