1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-01-11 18:13:52 +02:00

Remove showView and showBoard property drilling and migrate centerPanel to functional component (#649)

* Remove showView and showBoard property drilling and migrate centerPanel to functional component

* Make this backward compatible

* Fixing linter warnings

* Fixing call

* Fixing typescript types

* Addressing PR review comments

* Fixing cypress tests
This commit is contained in:
Jesús Espino 2021-07-13 13:42:05 +02:00 committed by GitHub
parent b791f288f1
commit 52062d452d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 204 additions and 232 deletions

View File

@ -5,6 +5,7 @@ Focalboard is an open source, self-hosted alternative to Trello, Notion, Asana a
This changelog summarizes updates to our open source project. You can also find the [latest releases and release notes on GitHub here](https://github.com/mattermost/focalboard/releases).
## [Work In Progress] - vNext Release - [Date TBD]
* Changed the urls to use routes instead of query parameters. Thanks @jespino!
## v0.8 Release - July, 2021
* CreatedBy property. Thanks @harshilsharma63!

View File

@ -66,18 +66,18 @@ const App = React.memo((): JSX.Element => {
<Route path='/change_password'>
<ChangePasswordPage/>
</Route>
<Route path='/shared'>
<Route path='/shared/:boardId?/:viewId?'>
<BoardPage readonly={true}/>
</Route>
<Route path='/board'>
<Route path='/board/:boardId?/:viewId?'>
{initialLoad && !user && <Redirect to='/login'/>}
<BoardPage/>
</Route>
<Route path='/workspace/:workspaceId/shared'>
<Route path='/workspace/:workspaceId/shared/:boardId?/:viewId?'>
<BoardPage readonly={true}/>
</Route>
<Route
path='/workspace/:workspaceId/'
path='/workspace/:workspaceId/:boardId?/:viewId?'
render={({match}) => {
if (initialLoad && !user) {
let redirectUrl = '/' + Utils.buildURL(`/workspace/${match.params.workspaceId}/`)
@ -92,7 +92,7 @@ const App = React.memo((): JSX.Element => {
)
}}
/>
<Route path='/'>
<Route path='/:boardId?/:viewId?'>
{initialLoad && !user && <Redirect to='/login'/>}
<BoardPage/>
</Route>

View File

@ -26,7 +26,6 @@ import Gallery from './gallery/gallery'
type Props = {
boardTree: BoardTree
showView: (id: string) => void
setSearchText: (text?: string) => void
intl: IntlShape
readonly: boolean
@ -95,7 +94,7 @@ class CenterPanel extends React.Component<Props, State> {
}
render(): JSX.Element {
const {boardTree, showView} = this.props
const {boardTree} = this.props
const {groupByProperty} = boardTree
const {activeView} = boardTree
@ -139,7 +138,6 @@ class CenterPanel extends React.Component<Props, State> {
/>
<ViewHeader
boardTree={boardTree}
showView={showView}
setSearchText={this.props.setSearchText}
addCard={() => this.addCard('', true)}
addCardFromTemplate={this.addCardFromTemplate}

View File

@ -19,8 +19,6 @@ import SidebarUserMenu from './sidebarUserMenu'
type Props = {
workspace?: IWorkspace
showBoard: (id?: string) => void
showView: (id: string, boardId?: string) => void
workspaceTree: WorkspaceTree,
activeBoardId?: string
}
@ -95,8 +93,6 @@ const Sidebar = React.memo((props: Props) => {
key={board.id}
views={views}
board={board}
showBoard={props.showBoard}
showView={props.showView}
activeBoardId={props.activeBoardId}
nextBoardId={nextBoardId}
/>
@ -108,7 +104,6 @@ const Sidebar = React.memo((props: Props) => {
<div className='octo-spacer'/>
<SidebarAddBoardMenu
showBoard={props.showBoard}
workspaceTree={props.workspaceTree}
activeBoardId={props.activeBoardId}
/>

View File

@ -1,7 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useState, useEffect} from 'react'
import React, {useState, useEffect, useCallback} from 'react'
import {FormattedMessage, useIntl, IntlShape} from 'react-intl'
import {generatePath, useHistory, useRouteMatch} from 'react-router-dom'
import {MutableBoard} from '../../blocks/board'
import {MutableBoardView} from '../../blocks/boardView'
@ -19,7 +20,6 @@ import BoardTemplateMenuItem from './boardTemplateMenuItem'
import './sidebarAddBoardMenu.scss'
type Props = {
showBoard: (id?: string) => void
workspaceTree: WorkspaceTree,
activeBoardId?: string
}
@ -69,6 +69,13 @@ const addBoardTemplateClicked = async (showBoard: (id: string) => void, activeBo
const SidebarAddBoardMenu = (props: Props): JSX.Element => {
const [globalTemplateTree, setGlobalTemplateTree] = useState<GlobalTemplateTree|null>(null)
const history = useHistory()
const match = useRouteMatch()
const showBoard = useCallback((boardId) => {
const newPath = generatePath(match.path, {...match.params, boardId: boardId || ''})
history.push(newPath)
}, [match, history])
useEffect(() => {
if (octoClient.workspaceId !== '0' && !globalTemplateTree) {
@ -114,7 +121,7 @@ const SidebarAddBoardMenu = (props: Props): JSX.Element => {
key={boardTemplate.id}
boardTemplate={boardTemplate}
isGlobal={false}
showBoard={props.showBoard}
showBoard={showBoard}
activeBoardId={props.activeBoardId}
/>
))}
@ -124,7 +131,7 @@ const SidebarAddBoardMenu = (props: Props): JSX.Element => {
key={boardTemplate.id}
boardTemplate={boardTemplate}
isGlobal={true}
showBoard={props.showBoard}
showBoard={showBoard}
activeBoardId={props.activeBoardId}
/>
))}
@ -133,14 +140,14 @@ const SidebarAddBoardMenu = (props: Props): JSX.Element => {
id='empty-template'
name={intl.formatMessage({id: 'Sidebar.empty-board', defaultMessage: 'Empty board'})}
icon={<BoardIcon/>}
onClick={() => addBoardClicked(props.showBoard, intl, props.activeBoardId)}
onClick={() => addBoardClicked(showBoard, intl, props.activeBoardId)}
/>
<Menu.Text
icon={<AddIcon/>}
id='add-template'
name={intl.formatMessage({id: 'Sidebar.add-template', defaultMessage: 'New template'})}
onClick={() => addBoardTemplateClicked(props.showBoard, props.activeBoardId)}
onClick={() => addBoardTemplateClicked(showBoard, props.activeBoardId)}
/>
</Menu>
</MenuWrapper>

View File

@ -1,7 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useState} from 'react'
import React, {useState, useCallback} from 'react'
import {FormattedMessage, useIntl} from 'react-intl'
import {generatePath, useHistory, useRouteMatch} from 'react-router-dom'
import {Board} from '../../blocks/board'
import {BoardView, IViewType, sortBoardViewsAlphabetically} from '../../blocks/boardView'
@ -22,8 +23,6 @@ import './sidebarBoardItem.scss'
type Props = {
views: readonly BoardView[]
board: Board
showBoard: (id?: string) => void
showView: (id: string, boardId?: string) => void
activeBoardId?: string
nextBoardId?: string
}
@ -31,6 +30,18 @@ type Props = {
const SidebarBoardItem = React.memo((props: Props) => {
const [collapsed, setCollapsed] = useState(false)
const intl = useIntl()
const history = useHistory()
const match = useRouteMatch()
const showBoard = useCallback((boardId) => {
const newPath = generatePath(match.path, {...match.params, boardId: boardId || ''})
history.push(newPath)
}, [match, history])
const showView = useCallback((viewId, boardId) => {
const newPath = generatePath(match.path, {...match.params, boardId: boardId || '', viewId: viewId || ''})
history.push(newPath)
}, [match, history])
const iconForViewType = (viewType: IViewType): JSX.Element => {
switch (viewType) {
@ -49,11 +60,11 @@ const SidebarBoardItem = React.memo((props: Props) => {
intl.formatMessage({id: 'Mutator.duplicate-board', defaultMessage: 'duplicate board'}),
false,
async (newBoardId) => {
props.showBoard(newBoardId)
showBoard(newBoardId)
},
async () => {
if (oldBoardId) {
props.showBoard(oldBoardId)
showBoard(oldBoardId)
}
},
)
@ -67,11 +78,11 @@ const SidebarBoardItem = React.memo((props: Props) => {
intl.formatMessage({id: 'Mutator.new-template-from-board', defaultMessage: 'new template from board'}),
true,
async (newBoardId) => {
props.showBoard(newBoardId)
showBoard(newBoardId)
},
async () => {
if (oldBoardId) {
props.showBoard(oldBoardId)
showBoard(oldBoardId)
}
},
)
@ -85,7 +96,7 @@ const SidebarBoardItem = React.memo((props: Props) => {
<div className='SidebarBoardItem'>
<div
className={'octo-sidebar-item ' + (collapsed ? 'collapsed' : 'expanded')}
onClick={() => props.showBoard(board.id)}
onClick={() => showBoard(board.id)}
>
<IconButton
icon={<DisclosureTriangle/>}
@ -97,7 +108,7 @@ const SidebarBoardItem = React.memo((props: Props) => {
>
{board.icon ? `${board.icon} ${displayTitle}` : displayTitle}
</div>
<MenuWrapper>
<MenuWrapper stopPropagationOnToggle={true}>
<IconButton icon={<OptionsIcon/>}/>
<Menu position='left'>
<Menu.Text
@ -111,11 +122,11 @@ const SidebarBoardItem = React.memo((props: Props) => {
async () => {
// This delay is needed because OctoListener has a default 100 ms notification delay before updates
setTimeout(() => {
props.showBoard(props.nextBoardId)
showBoard(props.nextBoardId)
}, 120)
},
async () => {
props.showBoard(board.id)
showBoard(board.id)
},
)
}}
@ -151,7 +162,7 @@ const SidebarBoardItem = React.memo((props: Props) => {
<div
key={view.id}
className='octo-sidebar-item subitem'
onClick={() => props.showView(view.id, board.id)}
onClick={() => showView(view.id, board.id)}
>
{iconForViewType(view.viewType)}
<div

View File

@ -26,7 +26,6 @@ import './viewHeader.scss'
type Props = {
boardTree: BoardTree
showView: (id: string) => void
setSearchText: (text?: string) => void
addCard: () => void
addCardFromTemplate: (cardTemplateId: string) => void
@ -38,7 +37,7 @@ type Props = {
const ViewHeader = React.memo((props: Props) => {
const [showFilter, setShowFilter] = useState(false)
const {boardTree, showView} = props
const {boardTree} = props
const {board, activeView} = boardTree
const withGroupBy = activeView.viewType === 'board' || activeView.viewType === 'table'
@ -72,7 +71,6 @@ const ViewHeader = React.memo((props: Props) => {
<ViewMenu
board={board}
boardTree={boardTree}
showView={showView}
readonly={props.readonly}
/>
</MenuWrapper>

View File

@ -1,10 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
import React, {useCallback} from 'react'
import {injectIntl, IntlShape} from 'react-intl'
import {generatePath, useHistory, useRouteMatch} from 'react-router-dom'
import {Board} from '../blocks/board'
import {IViewType, MutableBoardView} from '../blocks/boardView'
import {BoardView, IViewType, MutableBoardView} from '../blocks/boardView'
import {Constants} from '../constants'
import mutator from '../mutator'
import {Utils} from '../utils'
@ -20,19 +21,26 @@ import Menu from '../widgets/menu'
type Props = {
boardTree: BoardTree
board: Board,
showView: (id: string) => void
intl: IntlShape
readonly: boolean
}
export class ViewMenu extends React.PureComponent<Props> {
private handleDuplicateView = async () => {
const {boardTree, showView} = this.props
const ViewMenu = React.memo((props: Props) => {
const history = useHistory()
const match = useRouteMatch()
const showView = useCallback((viewId) => {
const newPath = generatePath(match.path, {...match.params, viewId: viewId || ''})
history.push(newPath)
}, [match, history])
const handleDuplicateView = useCallback(() => {
const {boardTree} = props
Utils.log('duplicateView')
const currentViewId = boardTree.activeView.id
const newView = boardTree.activeView.duplicate()
newView.title = `${boardTree.activeView.title} copy`
await mutator.insertBlock(
mutator.insertBlock(
newView,
'duplicate view',
async () => {
@ -45,31 +53,31 @@ export class ViewMenu extends React.PureComponent<Props> {
showView(currentViewId)
},
)
}
}, [props.boardTree, showView])
private handleDeleteView = async () => {
const {boardTree, showView} = this.props
const handleDeleteView = useCallback(() => {
const {boardTree} = props
Utils.log('deleteView')
const view = boardTree.activeView
const nextView = boardTree.views.find((o) => o !== view)
await mutator.deleteBlock(view, 'delete view')
mutator.deleteBlock(view, 'delete view')
if (nextView) {
showView(nextView.id)
}
}
}, [props.boardTree, showView])
private handleViewClick = (id: string) => {
const {boardTree, showView} = this.props
const handleViewClick = useCallback((id: string) => {
const {boardTree} = props
Utils.log('view ' + id)
const view = boardTree.views.find((o) => o.id === id)
Utils.assert(view, `view not found: ${id}`)
if (view) {
showView(view.id)
}
}
}, [props.boardTree, showView])
private handleAddViewBoard = async () => {
const {board, boardTree, showView, intl} = this.props
const handleAddViewBoard = useCallback(() => {
const {board, boardTree, intl} = props
Utils.log('addview-board')
const view = new MutableBoardView()
view.title = intl.formatMessage({id: 'View.NewBoardTitle', defaultMessage: 'Board view'})
@ -79,7 +87,7 @@ export class ViewMenu extends React.PureComponent<Props> {
const oldViewId = boardTree.activeView.id
await mutator.insertBlock(
mutator.insertBlock(
view,
'add view',
async () => {
@ -91,10 +99,10 @@ export class ViewMenu extends React.PureComponent<Props> {
async () => {
showView(oldViewId)
})
}
}, [props.boardTree, props.board, props.intl, showView])
private handleAddViewTable = async () => {
const {board, boardTree, showView, intl} = this.props
const handleAddViewTable = useCallback(() => {
const {board, boardTree, intl} = props
Utils.log('addview-table')
const view = new MutableBoardView()
@ -108,7 +116,7 @@ export class ViewMenu extends React.PureComponent<Props> {
const oldViewId = boardTree.activeView.id
await mutator.insertBlock(
mutator.insertBlock(
view,
'add view',
async () => {
@ -121,10 +129,10 @@ export class ViewMenu extends React.PureComponent<Props> {
async () => {
showView(oldViewId)
})
}
}, [props.boardTree, props.board, props.intl, showView])
private handleAddViewGallery = async () => {
const {board, boardTree, showView, intl} = this.props
const handleAddViewGallery = useCallback(() => {
const {board, boardTree, intl} = props
Utils.log('addview-gallery')
const view = new MutableBoardView()
@ -136,7 +144,7 @@ export class ViewMenu extends React.PureComponent<Props> {
const oldViewId = boardTree.activeView.id
await mutator.insertBlock(
mutator.insertBlock(
view,
'add view',
async () => {
@ -149,90 +157,32 @@ export class ViewMenu extends React.PureComponent<Props> {
async () => {
showView(oldViewId)
})
}
}, [props.board, props.boardTree, props.intl, showView])
render(): JSX.Element {
const {boardTree, intl} = this.props
const {boardTree, intl} = props
const duplicateViewText = intl.formatMessage({
id: 'View.DuplicateView',
defaultMessage: 'Duplicate View',
})
const deleteViewText = intl.formatMessage({
id: 'View.DeleteView',
defaultMessage: 'Delete View',
})
const addViewText = intl.formatMessage({
id: 'View.AddView',
defaultMessage: 'Add View',
})
const boardText = intl.formatMessage({
id: 'View.Board',
defaultMessage: 'Board',
})
const tableText = intl.formatMessage({
id: 'View.Table',
defaultMessage: 'Table',
})
const duplicateViewText = intl.formatMessage({
id: 'View.DuplicateView',
defaultMessage: 'Duplicate View',
})
const deleteViewText = intl.formatMessage({
id: 'View.DeleteView',
defaultMessage: 'Delete View',
})
const addViewText = intl.formatMessage({
id: 'View.AddView',
defaultMessage: 'Add View',
})
const boardText = intl.formatMessage({
id: 'View.Board',
defaultMessage: 'Board',
})
const tableText = intl.formatMessage({
id: 'View.Table',
defaultMessage: 'Table',
})
return (
<Menu>
{boardTree.views.map((view) => (
<Menu.Text
key={view.id}
id={view.id}
name={view.title}
icon={this.iconForViewType(view.viewType)}
onClick={this.handleViewClick}
/>))}
<Menu.Separator/>
{!this.props.readonly &&
<Menu.Text
id='__duplicateView'
name={duplicateViewText}
icon={<DuplicateIcon/>}
onClick={this.handleDuplicateView}
/>
}
{!this.props.readonly && boardTree.views.length > 1 &&
<Menu.Text
id='__deleteView'
name={deleteViewText}
icon={<DeleteIcon/>}
onClick={this.handleDeleteView}
/>
}
{!this.props.readonly &&
<Menu.SubMenu
id='__addView'
name={addViewText}
icon={<AddIcon/>}
>
<Menu.Text
id='board'
name={boardText}
icon={<BoardIcon/>}
onClick={this.handleAddViewBoard}
/>
<Menu.Text
id='table'
name={tableText}
icon={<TableIcon/>}
onClick={this.handleAddViewTable}
/>
<Menu.Text
id='gallery'
name='Gallery'
icon={<GalleryIcon/>}
onClick={this.handleAddViewGallery}
/>
</Menu.SubMenu>
}
</Menu>
)
}
private iconForViewType(viewType: IViewType) {
const iconForViewType = (viewType: IViewType) => {
switch (viewType) {
case 'board': return <BoardIcon/>
case 'table': return <TableIcon/>
@ -240,6 +190,62 @@ export class ViewMenu extends React.PureComponent<Props> {
default: return <div/>
}
}
}
return (
<Menu>
{boardTree.views.map((view: BoardView) => (
<Menu.Text
key={view.id}
id={view.id}
name={view.title}
icon={iconForViewType(view.viewType)}
onClick={handleViewClick}
/>))}
<Menu.Separator/>
{!props.readonly &&
<Menu.Text
id='__duplicateView'
name={duplicateViewText}
icon={<DuplicateIcon/>}
onClick={handleDuplicateView}
/>
}
{!props.readonly && boardTree.views.length > 1 &&
<Menu.Text
id='__deleteView'
name={deleteViewText}
icon={<DeleteIcon/>}
onClick={handleDeleteView}
/>
}
{!props.readonly &&
<Menu.SubMenu
id='__addView'
name={addViewText}
icon={<AddIcon/>}
>
<Menu.Text
id='board'
name={boardText}
icon={<BoardIcon/>}
onClick={handleAddViewBoard}
/>
<Menu.Text
id='table'
name={tableText}
icon={<TableIcon/>}
onClick={handleAddViewTable}
/>
<Menu.Text
id='gallery'
name='Gallery'
icon={<GalleryIcon/>}
onClick={handleAddViewGallery}
/>
</Menu.SubMenu>
}
</Menu>
)
})
export default injectIntl(ViewMenu)

View File

@ -17,14 +17,12 @@ type Props = {
workspace?: IWorkspace
workspaceTree: WorkspaceTree
boardTree?: BoardTree
showBoard: (id?: string) => void
showView: (id: string, boardId?: string) => void
setSearchText: (text?: string) => void
readonly: boolean
}
function centerContent(props: Props) {
const {workspace, boardTree, setSearchText, showView} = props
const {workspace, boardTree, setSearchText} = props
const {activeView} = boardTree || {}
if (boardTree && activeView) {
@ -32,7 +30,6 @@ function centerContent(props: Props) {
<CenterPanel
boardTree={boardTree}
setSearchText={setSearchText}
showView={showView}
readonly={props.readonly}
/>
)
@ -44,7 +41,7 @@ function centerContent(props: Props) {
}
const Workspace = React.memo((props: Props) => {
const {workspace, boardTree, workspaceTree, showBoard, showView} = props
const {workspace, boardTree, workspaceTree} = props
Utils.assert(workspaceTree || !props.readonly)
@ -53,8 +50,6 @@ const Workspace = React.memo((props: Props) => {
{!props.readonly &&
<Sidebar
workspace={workspace}
showBoard={showBoard}
showView={showView}
workspaceTree={workspaceTree}
activeBoardId={boardTree?.board.id}
/>

View File

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React from 'react'
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'
import {withRouter, RouteComponentProps} from 'react-router-dom'
import {generatePath, withRouter, RouteComponentProps} from 'react-router-dom'
import HotKeys from 'react-hot-keys'
import {IBlock} from '../blocks/block'
@ -18,14 +18,12 @@ import {MutableWorkspaceTree, WorkspaceTree} from '../viewModel/workspaceTree'
import './boardPage.scss'
import {IUser, WorkspaceUsersContext, WorkspaceUsers} from '../user'
type Props = RouteComponentProps<{workspaceId?: string}> & {
type Props = RouteComponentProps<{workspaceId?: string, boardId?: string, viewId?: string}> & {
readonly?: boolean
intl: IntlShape
}
type State = {
boardId: string
viewId: string
workspace?: IWorkspace,
workspaceTree: WorkspaceTree
boardTree?: BoardTree
@ -41,25 +39,30 @@ class BoardPage extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
// Backward compatible query param urls to regular urls
const queryString = new URLSearchParams(window.location.search)
let boardId = queryString.get('id') || ''
let viewId = queryString.get('v') || ''
if (!boardId) {
// Load last viewed boardView
boardId = localStorage.getItem('lastBoardId') || ''
viewId = localStorage.getItem('lastViewId') || ''
if (boardId) {
Utils.replaceUrlQueryParam('id', boardId)
const queryBoardId = queryString.get('id')
const queryViewId = queryString.get('v')
if (queryBoardId) {
const params = {...props.match.params, boardId: queryBoardId}
if (queryViewId) {
params.viewId = queryViewId
}
if (viewId) {
Utils.replaceUrlQueryParam('v', viewId)
const newPath = generatePath(props.match.path, params)
props.history.push(newPath)
}
if (!props.match.params.boardId) {
// Load last viewed boardView
const boardId = localStorage.getItem('lastBoardId') || undefined
const viewId = localStorage.getItem('lastViewId') || undefined
if (boardId) {
const newPath = generatePath(props.match.path, {...props.match.params, boardId, viewId})
props.history.push(newPath)
}
}
this.state = {
boardId,
viewId,
workspaceTree: new MutableWorkspaceTree(),
workspaceUsers: {
users: new Array<IUser>(),
@ -68,7 +71,7 @@ class BoardPage extends React.Component<Props, State> {
}
this.setWorkspaceUsers()
Utils.log(`BoardPage. boardId: ${boardId}`)
Utils.log(`BoardPage. boardId: ${props.match.params.boardId}`)
}
shouldComponentUpdate(): boolean {
@ -153,15 +156,15 @@ class BoardPage extends React.Component<Props, State> {
}
componentDidMount(): void {
if (this.state.boardId) {
this.attachToBoard(this.state.boardId, this.state.viewId)
if (this.props.match.params.boardId) {
this.attachToBoard(this.props.match.params.boardId, this.props.match.params.viewId)
} else {
this.sync()
}
}
componentWillUnmount(): void {
Utils.log(`boardPage.componentWillUnmount: ${this.state.boardId}`)
Utils.log(`boardPage.componentWillUnmount: ${this.props.match.params.boardId}`)
this.workspaceListener.close()
}
@ -211,12 +214,6 @@ class BoardPage extends React.Component<Props, State> {
workspace={workspace}
workspaceTree={workspaceTree}
boardTree={this.state.boardTree}
showView={(id, boardId) => {
this.showView(id, boardId)
}}
showBoard={(id) => {
this.showBoard(id)
}}
setSearchText={(text) => {
this.setSearchText(text)
}}
@ -233,19 +230,15 @@ class BoardPage extends React.Component<Props, State> {
localStorage.setItem('lastViewId', viewId)
if (boardId) {
this.sync(boardId, viewId)
this.sync()
} else {
// No board
this.setState({
boardTree: undefined,
boardId: '',
viewId: '',
})
const newPath = generatePath(this.props.match.path, {...this.props.match.params, boardId: '', viewId: ''})
this.props.history.push(newPath)
}
}
private async sync(boardId: string = this.state.boardId, viewId: string | undefined = this.state.viewId) {
Utils.log(`sync start: ${boardId}`)
private async sync() {
Utils.log(`sync start: ${this.props.match.params.boardId}`)
let workspace: IWorkspace | undefined
if (!this.props.readonly) {
@ -265,7 +258,7 @@ class BoardPage extends React.Component<Props, State> {
boardIdsToListen = ['', ...boardIds]
} else {
// Read-only view
boardIdsToListen = [this.state.boardId]
boardIdsToListen = [this.props.match.params.boardId || '']
}
// Listen to boards plus all blocks at root (Empty string for parentId)
@ -304,21 +297,20 @@ class BoardPage extends React.Component<Props, State> {
},
)
if (boardId) {
const boardTree = await MutableBoardTree.sync(boardId, viewId)
if (this.props.match.params.boardId) {
const boardTree = await MutableBoardTree.sync(this.props.match.params.boardId || '', this.props.match.params.viewId || '')
if (boardTree && boardTree.board) {
// Update url with viewId if it's different
if (boardTree.activeView.id !== this.state.viewId) {
Utils.replaceUrlQueryParam('v', boardTree.activeView.id)
if (boardTree.activeView.id !== this.props.match.params.viewId) {
const newPath = generatePath(this.props.match.path, {...this.props.match.params, viewId: boardTree.activeView.id})
this.props.history.push(newPath)
}
// TODO: Handle error (viewId not found)
this.setState({
boardTree,
boardId,
viewId: boardTree.activeView!.id,
syncFailed: false,
})
Utils.log(`sync complete: ${boardTree.board?.id} (${boardTree.board?.title})`)
@ -326,18 +318,17 @@ class BoardPage extends React.Component<Props, State> {
// Board may have been deleted
this.setState({
boardTree: undefined,
viewId: '',
syncFailed: true,
})
Utils.log(`sync complete: board ${boardId} not found`)
Utils.log(`sync complete: board ${this.props.match.params.boardId} not found`)
}
}
}
private async incrementalUpdate(blocks: IBlock[]) {
const {workspaceTree, boardTree, viewId} = this.state
const {workspaceTree, boardTree} = this.state
let newState = {workspaceTree, boardTree, viewId}
let newState = {workspaceTree, boardTree}
const newWorkspaceTree = MutableWorkspaceTree.incrementalUpdate(workspaceTree, blocks)
if (newWorkspaceTree) {
@ -347,57 +338,27 @@ class BoardPage extends React.Component<Props, State> {
let newBoardTree: BoardTree | undefined
if (boardTree) {
newBoardTree = await MutableBoardTree.incrementalUpdate(boardTree, blocks)
} else if (this.state.boardId) {
} else if (this.props.match.params.boardId) {
// Corner case: When the page is viewing a deleted board, that is subsequently un-deleted on another client
newBoardTree = await MutableBoardTree.sync(this.state.boardId, this.state.viewId)
newBoardTree = await MutableBoardTree.sync(this.props.match.params.boardId || '', this.props.match.params.viewId || '')
}
if (newBoardTree) {
newState = {...newState, boardTree: newBoardTree, viewId: newBoardTree.activeView.id}
newState = {...newState, boardTree: newBoardTree}
} else {
newState = {...newState, boardTree: undefined}
}
// Update url with viewId if it's different
if (newBoardTree && newBoardTree.activeView.id !== this.state.viewId) {
Utils.replaceUrlQueryParam('v', newBoardTree?.activeView.id)
if (newBoardTree && newBoardTree.activeView.id !== this.props.match.params.viewId) {
const newPath = generatePath(this.props.match.path, {...this.props.match.params, viewId: newBoardTree?.activeView.id})
this.props.history.push(newPath)
}
this.setState(newState)
}
// IPageController
showBoard(boardId?: string): void {
const {boardTree} = this.state
if (boardTree?.board?.id === boardId) {
return
}
const newUrl = new URL(window.location.toString())
newUrl.searchParams.set('id', boardId || '')
newUrl.searchParams.set('v', '')
window.history.pushState({path: newUrl.toString()}, '', newUrl.toString())
this.attachToBoard(boardId)
}
async showView(viewId: string, boardId: string = this.state.boardId): Promise<void> {
localStorage.setItem('lastViewId', viewId)
if (this.state.boardTree && this.state.boardId === boardId) {
const newBoardTree = await this.state.boardTree.copyWithView(viewId)
this.setState({boardTree: newBoardTree, viewId})
} else {
this.attachToBoard(boardId, viewId)
}
const newUrl = new URL(window.location.toString())
newUrl.searchParams.set('id', boardId)
newUrl.searchParams.set('v', viewId)
window.history.pushState({path: newUrl.toString()}, '', newUrl.toString())
}
async setSearchText(text?: string): Promise<void> {
if (!this.state.boardTree) {
Utils.assertFailure('setSearchText: boardTree')