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:
parent
b791f288f1
commit
52062d452d
@ -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!
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user