1
0
mirror of https://github.com/mattermost/focalboard.git synced 2025-02-13 19:42:12 +02:00

Splitting the sidebar board item into its own functional component

This commit is contained in:
Jesús Espino 2021-03-27 18:44:00 +01:00
parent bd7bd50e79
commit 852269e20e
2 changed files with 181 additions and 156 deletions

View File

@ -1,28 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react'
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'
import {injectIntl, IntlShape} from 'react-intl'
import {Board, MutableBoard} from '../../blocks/board'
import {BoardView, IViewType, MutableBoardView} from '../../blocks/boardView'
import {Constants} from '../../constants'
import mutator from '../../mutator'
import octoClient from '../../octoClient'
import {loadTheme, setTheme, Theme} from '../../theme'
import {loadTheme} from '../../theme'
import {IUser, UserContext} from '../../user'
import {WorkspaceTree} from '../../viewModel/workspaceTree'
import IconButton from '../../widgets/buttons/iconButton'
import BoardIcon from '../../widgets/icons/board'
import DeleteIcon from '../../widgets/icons/delete'
import DisclosureTriangle from '../../widgets/icons/disclosureTriangle'
import DuplicateIcon from '../../widgets/icons/duplicate'
import HamburgerIcon from '../../widgets/icons/hamburger'
import HideSidebarIcon from '../../widgets/icons/hideSidebar'
import LogoWithNameIcon from '../../widgets/icons/logoWithName'
import LogoWithNameWhiteIcon from '../../widgets/icons/logoWithNameWhite'
import OptionsIcon from '../../widgets/icons/options'
import ShowSidebarIcon from '../../widgets/icons/showSidebar'
import TableIcon from '../../widgets/icons/table'
import Menu from '../../widgets/menu'
import MenuWrapper from '../../widgets/menuWrapper'
@ -31,6 +22,7 @@ import RegistrationLink from '../registrationLink'
import SidebarSettingsMenu from './sidebarSettingsMenu'
import SidebarAddBoardMenu from './sidebarAddBoardMenu'
import SidebarBoardItem from './sidebarBoardItem'
import './sidebar.scss'
type Props = {
@ -44,7 +36,6 @@ type Props = {
type State = {
isHidden: boolean
collapsedBoards: {[key: string]: boolean}
showRegistrationLinkDialog?: boolean
whiteLogo: boolean
}
@ -52,7 +43,7 @@ type State = {
class Sidebar extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {isHidden: false, collapsedBoards: {}, whiteLogo: false}
this.state = {isHidden: false, whiteLogo: false}
}
shouldComponentUpdate(): boolean {
@ -78,7 +69,6 @@ class Sidebar extends React.Component<Props, State> {
}
const {boards, views} = workspaceTree
const {collapsedBoards} = this.state
if (this.state.isHidden) {
return (
@ -121,97 +111,17 @@ class Sidebar extends React.Component<Props, State> {
<div className='octo-sidebar-list'>
{
boards.map((board) => {
const displayTitle: string = board.title || intl.formatMessage({id: 'Sidebar.untitled-board', defaultMessage: '(Untitled Board)'})
const boardViews = views.filter((view) => view.parentId === board.id)
const nextBoardId = boards.length > 1 ? boards.find((o) => o.id !== board.id)?.id : undefined
return (
<div key={board.id}>
<div className={'octo-sidebar-item ' + (collapsedBoards[board.id] ? 'collapsed' : 'expanded')}>
<IconButton
icon={<DisclosureTriangle/>}
onClick={() => {
const newCollapsedBoards = {...this.state.collapsedBoards}
newCollapsedBoards[board.id] = !newCollapsedBoards[board.id]
this.setState({collapsedBoards: newCollapsedBoards})
}}
/>
<div
className='octo-sidebar-title'
onClick={() => {
this.boardClicked(board)
}}
title={displayTitle}
>
{board.icon ? `${board.icon} ${displayTitle}` : displayTitle}
</div>
<MenuWrapper>
<IconButton icon={<OptionsIcon/>}/>
<Menu position='left'>
<Menu.Text
id='deleteBoard'
name={intl.formatMessage({id: 'Sidebar.delete-board', defaultMessage: 'Delete board'})}
icon={<DeleteIcon/>}
onClick={async () => {
const nextBoardId = boards.length > 1 ? boards.find((o) => o.id !== board.id)?.id : undefined
mutator.deleteBlock(
board,
intl.formatMessage({id: 'Sidebar.delete-board', defaultMessage: 'Delete board'}),
async () => {
// This delay is needed because OctoListener has a default 100 ms notification delay before updates
setTimeout(() => {
this.props.showBoard(nextBoardId)
}, 120)
},
async () => {
this.props.showBoard(board.id)
},
)
}}
/>
<Menu.Text
id='duplicateBoard'
name={intl.formatMessage({id: 'Sidebar.duplicate-board', defaultMessage: 'Duplicate board'})}
icon={<DuplicateIcon/>}
onClick={() => {
this.duplicateBoard(board.id)
}}
/>
<Menu.Text
id='templateFromBoard'
name={intl.formatMessage({id: 'Sidebar.template-from-board', defaultMessage: 'New template from board'})}
onClick={() => {
this.addTemplateFromBoard(board.id)
}}
/>
</Menu>
</MenuWrapper>
</div>
{!collapsedBoards[board.id] && boardViews.length === 0 &&
<div className='octo-sidebar-item subitem no-views'>
<FormattedMessage
id='Sidebar.no-views-in-board'
defaultMessage='No pages inside'
/>
</div>}
{!collapsedBoards[board.id] && boardViews.map((view) => (
<div
key={view.id}
className='octo-sidebar-item subitem'
>
{this.iconForViewType(view.viewType)}
<div
className='octo-sidebar-title'
onClick={() => {
this.viewClicked(board, view)
}}
title={view.title || intl.formatMessage({id: 'Sidebar.untitled-view', defaultMessage: '(Untitled View)'})}
>
{view.title || intl.formatMessage({id: 'Sidebar.untitled-view', defaultMessage: '(Untitled View)'})}
</div>
</div>
))}
</div>
<SidebarBoardItem
key={board.id}
views={views}
board={board}
showBoard={this.props.showBoard}
showView={this.props.showView}
activeBoardId={this.props.activeBoardId}
nextBoardId={nextBoardId}
/>
)
})
}
@ -297,58 +207,6 @@ class Sidebar extends React.Component<Props, State> {
)
}
private boardClicked(board: Board): void {
this.props.showBoard(board.id)
}
private viewClicked(board: Board, view: BoardView): void {
this.props.showView(view.id, board.id)
}
private iconForViewType(viewType: IViewType): JSX.Element {
switch (viewType) {
case 'board': return <BoardIcon/>
case 'table': return <TableIcon/>
default: return <div/>
}
}
private async duplicateBoard(boardId: string) {
const oldBoardId = this.props.activeBoardId
await mutator.duplicateBoard(
boardId,
this.props.intl.formatMessage({id: 'Mutator.duplicate-board', defaultMessage: 'duplicate board'}),
false,
async (newBoardId) => {
this.props.showBoard(newBoardId)
},
async () => {
if (oldBoardId) {
this.props.showBoard(oldBoardId)
}
},
)
}
private async addTemplateFromBoard(boardId: string) {
const oldBoardId = this.props.activeBoardId
await mutator.duplicateBoard(
boardId,
this.props.intl.formatMessage({id: 'Mutator.new-template-from-board', defaultMessage: 'new template from board'}),
true,
async (newBoardId) => {
this.props.showBoard(newBoardId)
},
async () => {
if (oldBoardId) {
this.props.showBoard(oldBoardId)
}
},
)
}
private hideClicked = () => {
this.setState({isHidden: true})
}

View File

@ -0,0 +1,167 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useState} from 'react'
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'
import {Board} from '../../blocks/board'
import {BoardView, IViewType} from '../../blocks/boardView'
import mutator from '../../mutator'
import IconButton from '../../widgets/buttons/iconButton'
import BoardIcon from '../../widgets/icons/board'
import DeleteIcon from '../../widgets/icons/delete'
import DisclosureTriangle from '../../widgets/icons/disclosureTriangle'
import DuplicateIcon from '../../widgets/icons/duplicate'
import OptionsIcon from '../../widgets/icons/options'
import TableIcon from '../../widgets/icons/table'
import Menu from '../../widgets/menu'
import MenuWrapper from '../../widgets/menuWrapper'
type Props = {
views: readonly BoardView[]
board: Board
showBoard: (id?: string) => void
showView: (id: string, boardId?: string) => void
activeBoardId?: string
intl: IntlShape
nextBoardId?: string
}
const SidebarBoardItem = React.memo((props: Props) => {
const [collapsed, setCollapsed] = useState(false)
const iconForViewType = (viewType: IViewType): JSX.Element => {
switch (viewType) {
case 'board': return <BoardIcon/>
case 'table': return <TableIcon/>
default: return <div/>
}
}
const duplicateBoard = async (boardId: string) => {
const oldBoardId = props.activeBoardId
await mutator.duplicateBoard(
boardId,
props.intl.formatMessage({id: 'Mutator.duplicate-board', defaultMessage: 'duplicate board'}),
false,
async (newBoardId) => {
props.showBoard(newBoardId)
},
async () => {
if (oldBoardId) {
props.showBoard(oldBoardId)
}
},
)
}
const addTemplateFromBoard = async (boardId: string) => {
const oldBoardId = props.activeBoardId
await mutator.duplicateBoard(
boardId,
props.intl.formatMessage({id: 'Mutator.new-template-from-board', defaultMessage: 'new template from board'}),
true,
async (newBoardId) => {
props.showBoard(newBoardId)
},
async () => {
if (oldBoardId) {
props.showBoard(oldBoardId)
}
},
)
}
const {board, intl, views} = props
const displayTitle: string = board.title || intl.formatMessage({id: 'Sidebar.untitled-board', defaultMessage: '(Untitled Board)'})
const boardViews = views.filter((view) => view.parentId === board.id)
return (
<div key={board.id}>
<div className={'octo-sidebar-item ' + (collapsed ? 'collapsed' : 'expanded')}>
<IconButton
icon={<DisclosureTriangle/>}
onClick={() => setCollapsed(!collapsed)}
/>
<div
className='octo-sidebar-title'
onClick={() => {
props.showBoard(board.id)
}}
title={displayTitle}
>
{board.icon ? `${board.icon} ${displayTitle}` : displayTitle}
</div>
<MenuWrapper>
<IconButton icon={<OptionsIcon/>}/>
<Menu position='left'>
<Menu.Text
id='deleteBoard'
name={intl.formatMessage({id: 'Sidebar.delete-board', defaultMessage: 'Delete board'})}
icon={<DeleteIcon/>}
onClick={async () => {
mutator.deleteBlock(
board,
intl.formatMessage({id: 'Sidebar.delete-board', defaultMessage: 'Delete board'}),
async () => {
// This delay is needed because OctoListener has a default 100 ms notification delay before updates
setTimeout(() => {
props.showBoard(props.nextBoardId)
}, 120)
},
async () => {
props.showBoard(board.id)
},
)
}}
/>
<Menu.Text
id='duplicateBoard'
name={intl.formatMessage({id: 'Sidebar.duplicate-board', defaultMessage: 'Duplicate board'})}
icon={<DuplicateIcon/>}
onClick={() => {
duplicateBoard(board.id)
}}
/>
<Menu.Text
id='templateFromBoard'
name={intl.formatMessage({id: 'Sidebar.template-from-board', defaultMessage: 'New template from board'})}
onClick={() => {
addTemplateFromBoard(board.id)
}}
/>
</Menu>
</MenuWrapper>
</div>
{!collapsed && boardViews.length === 0 &&
<div className='octo-sidebar-item subitem no-views'>
<FormattedMessage
id='Sidebar.no-views-in-board'
defaultMessage='No pages inside'
/>
</div>}
{!collapsed && boardViews.map((view) => (
<div
key={view.id}
className='octo-sidebar-item subitem'
>
{iconForViewType(view.viewType)}
<div
className='octo-sidebar-title'
onClick={() => {
props.showView(view.id, board.id)
}}
title={view.title || intl.formatMessage({id: 'Sidebar.untitled-view', defaultMessage: '(Untitled View)'})}
>
{view.title || intl.formatMessage({id: 'Sidebar.untitled-view', defaultMessage: '(Untitled View)'})}
</div>
</div>
))}
</div>
)
})
export default injectIntl(SidebarBoardItem)