GH-1333: Implement Screen 3 and 4 of v6.0 navigation and FTUE (#1341)
This commit is contained in:
parent
319dcbfb5d
commit
c9aeeb38bf
|
@ -1,14 +1,90 @@
|
||||||
.EmptyCenterPanel {
|
.EmptyCenterPanel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
padding: 80px;
|
padding: 80px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.7);
|
color: rgba(var(--center-channel-color-rgb));
|
||||||
|
|
||||||
.WorkspaceInfo {
|
.WorkspaceInfo {
|
||||||
b {
|
b {
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-items: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 25px;
|
||||||
|
color: var(--sys-center-channel-color);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
color: var(--sys-center-channel-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose-template-text {
|
||||||
|
color: var(--center-channel-color);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: flex;
|
||||||
|
justify-items: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 32px;
|
||||||
|
border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
||||||
|
box-sizing: border-box;
|
||||||
|
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.08);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 20px;
|
||||||
|
max-width: 300px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-right: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-template {
|
||||||
|
border: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
color: var(--button-bg);
|
||||||
|
|
||||||
|
i {
|
||||||
|
background: var(--button-bg);
|
||||||
|
color: var(--button-color);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,173 @@
|
||||||
// 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, {useCallback, useEffect} from 'react'
|
||||||
import {FormattedMessage} from 'react-intl'
|
import {FormattedMessage, useIntl} from 'react-intl'
|
||||||
|
import {generatePath, useHistory, useRouteMatch} from 'react-router-dom'
|
||||||
|
|
||||||
import {getCurrentWorkspace} from '../store/workspace'
|
import {getCurrentWorkspace} from '../store/workspace'
|
||||||
import {useAppSelector} from '../store/hooks'
|
import {useAppSelector, useAppDispatch} from '../store/hooks'
|
||||||
|
import {Utils} from '../utils'
|
||||||
|
import {Board} from '../blocks/board'
|
||||||
|
import {getGlobalTemplates, fetchGlobalTemplates} from '../store/globalTemplates'
|
||||||
|
import {getSortedTemplates} from '../store/boards'
|
||||||
|
import AddIcon from '../widgets/icons/add'
|
||||||
|
import BoardIcon from '../widgets/icons/board'
|
||||||
|
import octoClient from '../octoClient'
|
||||||
|
|
||||||
|
import {addBoardTemplateClicked, addBoardClicked} from './sidebar/sidebarAddBoardMenu'
|
||||||
|
import {addBoardFromTemplate, BoardTemplateButtonMenu} from './sidebar/boardTemplateMenuItem'
|
||||||
|
|
||||||
import './emptyCenterPanel.scss'
|
import './emptyCenterPanel.scss'
|
||||||
|
|
||||||
const EmptyCenterPanel = React.memo(() => {
|
type ButtonProps = {
|
||||||
const workspace = useAppSelector(getCurrentWorkspace)
|
buttonIcon: string | React.ReactNode,
|
||||||
|
title: string,
|
||||||
|
readonly: boolean,
|
||||||
|
onClick: () => void,
|
||||||
|
showBoard?: (boardId: string) => void
|
||||||
|
boardTemplate?: Board
|
||||||
|
classNames?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const PanelButton = React.memo((props: ButtonProps) => {
|
||||||
|
const {onClick, buttonIcon, title, readonly, showBoard, boardTemplate, classNames} = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='EmptyCenterPanel'>
|
<div
|
||||||
{workspace && workspace.id !== '0' &&
|
onClick={onClick}
|
||||||
<div className='WorkspaceInfo'>
|
className={`button ${classNames || ''}`}
|
||||||
<FormattedMessage
|
>
|
||||||
id='EmptyCenterPanel.workspace'
|
<span>{buttonIcon}</span>
|
||||||
defaultMessage='This is the workspace for:'
|
<span className='button-title'>{title}</span>
|
||||||
|
{!readonly && showBoard && boardTemplate &&
|
||||||
|
<BoardTemplateButtonMenu
|
||||||
|
showBoard={showBoard}
|
||||||
|
boardTemplate={boardTemplate}
|
||||||
/>
|
/>
|
||||||
<b>
|
|
||||||
{workspace.title}
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
<div className='Hint'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='EmptyCenterPanel.no-content'
|
|
||||||
defaultMessage='Add or select a board from the sidebar to get started.'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const EmptyCenterPanel = React.memo(() => {
|
||||||
|
const workspace = useAppSelector(getCurrentWorkspace)
|
||||||
|
const templates = useAppSelector(getSortedTemplates)
|
||||||
|
const globalTemplates = useAppSelector<Board[]>(getGlobalTemplates)
|
||||||
|
const history = useHistory()
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const intl = useIntl()
|
||||||
|
const match = useRouteMatch<{boardId: string, viewId?: string}>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (octoClient.workspaceId !== '0' && globalTemplates.length === 0) {
|
||||||
|
dispatch(fetchGlobalTemplates())
|
||||||
|
}
|
||||||
|
}, [octoClient.workspaceId])
|
||||||
|
|
||||||
|
const showBoard = useCallback((boardId) => {
|
||||||
|
const params = {...match.params, boardId: boardId || ''}
|
||||||
|
delete params.viewId
|
||||||
|
const newPath = generatePath(match.path, params)
|
||||||
|
history.push(newPath)
|
||||||
|
}, [match, history])
|
||||||
|
|
||||||
|
const newTemplateClicked = () => addBoardTemplateClicked(showBoard, intl)
|
||||||
|
const emptyBoardClicked = () => addBoardClicked(showBoard, intl)
|
||||||
|
|
||||||
|
if (!Utils.isFocalboardPlugin()) {
|
||||||
|
return (
|
||||||
|
<div className='EmptyCenterPanel'>
|
||||||
|
<div className='Hint'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='EmptyCenterPanel.no-content'
|
||||||
|
defaultMessage='Add or select a board from the sidebar to get started.'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='EmptyCenterPanel'>
|
||||||
|
<div className='content'>
|
||||||
|
<span className='title'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='EmptyCenterPanel.plugin.no-content-title'
|
||||||
|
defaultMessage='Create a Board in {workspaceName}'
|
||||||
|
values={{workspaceName: workspace?.title}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className='description'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='EmptyCenterPanel.plugin.no-content-description'
|
||||||
|
defaultMessage='Add a board to the sidebar using any of the templates defined below or start from scratch.{lineBreak} Members of "{workspaceName}" will have access to boards created here.'
|
||||||
|
values={{
|
||||||
|
workspaceName: <b>{workspace?.title}</b>,
|
||||||
|
lineBreak: <br/>,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className='choose-template-text'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='EmptyCenterPanel.plugin.choose-a-template'
|
||||||
|
defaultMessage='Choose a template'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<div className='button-container'>
|
||||||
|
{templates.map((template) =>
|
||||||
|
(
|
||||||
|
<PanelButton
|
||||||
|
key={template.id}
|
||||||
|
title={template.title}
|
||||||
|
buttonIcon={template.fields.icon}
|
||||||
|
readonly={false}
|
||||||
|
onClick={() => addBoardFromTemplate(intl, showBoard, template.id)}
|
||||||
|
showBoard={showBoard}
|
||||||
|
boardTemplate={template}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
{globalTemplates.map((template) =>
|
||||||
|
(
|
||||||
|
<PanelButton
|
||||||
|
key={template.id}
|
||||||
|
title={template.title}
|
||||||
|
buttonIcon={template.fields.icon}
|
||||||
|
readonly={true}
|
||||||
|
onClick={() => addBoardFromTemplate(intl, showBoard, template.id, undefined, true)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
<PanelButton
|
||||||
|
key={'new-template'}
|
||||||
|
title={intl.formatMessage({id: 'EmptyCenterPanel.plugin.new-template', defaultMessage: 'New template'})}
|
||||||
|
buttonIcon={<AddIcon/>}
|
||||||
|
readonly={true}
|
||||||
|
onClick={newTemplateClicked}
|
||||||
|
classNames='new-template'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className='choose-template-text'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='EmptyCenterPanel.plugin.no-content-or'
|
||||||
|
defaultMessage='or'
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<PanelButton
|
||||||
|
key={'start-with-an-empty-board'}
|
||||||
|
title={intl.formatMessage({id: 'EmptyCenterPanel.plugin.empty-board', defaultMessage: 'Start with an Empty Board'})}
|
||||||
|
buttonIcon={<BoardIcon/>}
|
||||||
|
readonly={true}
|
||||||
|
onClick={emptyBoardClicked}
|
||||||
|
/>
|
||||||
|
<FormattedMessage
|
||||||
|
id='EmptyCenterPanel.plugin.end-message'
|
||||||
|
defaultMessage='You can change the channel using the switcher in the sidebar.'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
export default EmptyCenterPanel
|
export default EmptyCenterPanel
|
||||||
|
|
|
@ -13,14 +13,7 @@ import Menu from '../../widgets/menu'
|
||||||
import MenuWrapper from '../../widgets/menuWrapper'
|
import MenuWrapper from '../../widgets/menuWrapper'
|
||||||
import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../telemetry/telemetryClient'
|
import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../telemetry/telemetryClient'
|
||||||
|
|
||||||
type Props = {
|
export const addBoardFromTemplate = async (intl: IntlShape, showBoard: (id: string) => void, boardTemplateId: string, activeBoardId?: string, global = false) => {
|
||||||
boardTemplate: Board
|
|
||||||
isGlobal: boolean
|
|
||||||
showBoard: (id: string) => void
|
|
||||||
activeBoardId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const addBoardFromTemplate = async (intl: IntlShape, showBoard: (id: string) => void, boardTemplateId: string, activeBoardId?: string, global = false) => {
|
|
||||||
const oldBoardId = activeBoardId
|
const oldBoardId = activeBoardId
|
||||||
const afterRedo = async (newBoardId: string) => {
|
const afterRedo = async (newBoardId: string) => {
|
||||||
showBoard(newBoardId)
|
showBoard(newBoardId)
|
||||||
|
@ -41,8 +34,49 @@ const addBoardFromTemplate = async (intl: IntlShape, showBoard: (id: string) =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ButtonProps = {
|
||||||
|
showBoard: (id: string) => void
|
||||||
|
boardTemplate: Board
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BoardTemplateButtonMenu = React.memo((props: ButtonProps) => {
|
||||||
|
const intl = useIntl()
|
||||||
|
const {showBoard, boardTemplate} = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuWrapper stopPropagationOnToggle={true}>
|
||||||
|
<IconButton icon={<OptionsIcon/>}/>
|
||||||
|
<Menu position='right'>
|
||||||
|
<Menu.Text
|
||||||
|
icon={<EditIcon/>}
|
||||||
|
id='edit'
|
||||||
|
name={intl.formatMessage({id: 'Sidebar.edit-template', defaultMessage: 'Edit'})}
|
||||||
|
onClick={() => {
|
||||||
|
showBoard(boardTemplate.id || '')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Menu.Text
|
||||||
|
icon={<DeleteIcon/>}
|
||||||
|
id='delete'
|
||||||
|
name={intl.formatMessage({id: 'Sidebar.delete-template', defaultMessage: 'Delete'})}
|
||||||
|
onClick={async () => {
|
||||||
|
await mutator.deleteBlock(boardTemplate, 'delete board template')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
</MenuWrapper>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
boardTemplate: Board
|
||||||
|
isGlobal: boolean
|
||||||
|
showBoard: (id: string) => void
|
||||||
|
activeBoardId?: string
|
||||||
|
}
|
||||||
|
|
||||||
const BoardTemplateMenuItem = React.memo((props: Props) => {
|
const BoardTemplateMenuItem = React.memo((props: Props) => {
|
||||||
const {boardTemplate, isGlobal, activeBoardId} = props
|
const {boardTemplate, isGlobal, activeBoardId, showBoard} = props
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
const displayName = boardTemplate.title || intl.formatMessage({id: 'Sidebar.untitled', defaultMessage: 'Untitled'})
|
const displayName = boardTemplate.title || intl.formatMessage({id: 'Sidebar.untitled', defaultMessage: 'Untitled'})
|
||||||
|
@ -54,30 +88,13 @@ const BoardTemplateMenuItem = React.memo((props: Props) => {
|
||||||
name={displayName}
|
name={displayName}
|
||||||
icon={<div className='Icon'>{boardTemplate.fields.icon}</div>}
|
icon={<div className='Icon'>{boardTemplate.fields.icon}</div>}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
addBoardFromTemplate(intl, props.showBoard, boardTemplate.id || '', activeBoardId, isGlobal)
|
addBoardFromTemplate(intl, showBoard, boardTemplate.id || '', activeBoardId, isGlobal)
|
||||||
}}
|
}}
|
||||||
rightIcon={!isGlobal &&
|
rightIcon={!isGlobal &&
|
||||||
<MenuWrapper stopPropagationOnToggle={true}>
|
<BoardTemplateButtonMenu
|
||||||
<IconButton icon={<OptionsIcon/>}/>
|
boardTemplate={boardTemplate}
|
||||||
<Menu position='right'>
|
showBoard={showBoard}
|
||||||
<Menu.Text
|
/>
|
||||||
icon={<EditIcon/>}
|
|
||||||
id='edit'
|
|
||||||
name={intl.formatMessage({id: 'Sidebar.edit-template', defaultMessage: 'Edit'})}
|
|
||||||
onClick={() => {
|
|
||||||
props.showBoard(boardTemplate.id || '')
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Menu.Text
|
|
||||||
icon={<DeleteIcon/>}
|
|
||||||
id='delete'
|
|
||||||
name={intl.formatMessage({id: 'Sidebar.delete-template', defaultMessage: 'Delete'})}
|
|
||||||
onClick={async () => {
|
|
||||||
await mutator.deleteBlock(boardTemplate, 'delete board template')
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Menu>
|
|
||||||
</MenuWrapper>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -145,7 +145,7 @@ const Sidebar = React.memo((props: Props) => {
|
||||||
<div className='octo-spacer'/>
|
<div className='octo-spacer'/>
|
||||||
|
|
||||||
{
|
{
|
||||||
!props.isDashboard &&
|
(!props.isDashboard && !Utils.isFocalboardPlugin()) &&
|
||||||
<SidebarAddBoardMenu
|
<SidebarAddBoardMenu
|
||||||
activeBoardId={props.activeBoardId}
|
activeBoardId={props.activeBoardId}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -24,7 +24,7 @@ type Props = {
|
||||||
activeBoardId?: string
|
activeBoardId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const addBoardClicked = async (showBoard: (id: string) => void, intl: IntlShape, activeBoardId?: string) => {
|
export const addBoardClicked = async (showBoard: (id: string) => void, intl: IntlShape, activeBoardId?: string) => {
|
||||||
const oldBoardId = activeBoardId
|
const oldBoardId = activeBoardId
|
||||||
|
|
||||||
const board = createBoard()
|
const board = createBoard()
|
||||||
|
@ -50,10 +50,11 @@ const addBoardClicked = async (showBoard: (id: string) => void, intl: IntlShape,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const addBoardTemplateClicked = async (showBoard: (id: string) => void, intl: IntlShape, activeBoardId?: string) => {
|
export const addBoardTemplateClicked = async (showBoard: (id: string) => void, intl: IntlShape, activeBoardId?: string) => {
|
||||||
const boardTemplate = createBoard()
|
const boardTemplate = createBoard()
|
||||||
boardTemplate.rootId = boardTemplate.id
|
boardTemplate.rootId = boardTemplate.id
|
||||||
boardTemplate.fields.isTemplate = true
|
boardTemplate.fields.isTemplate = true
|
||||||
|
boardTemplate.title = intl.formatMessage({id: 'View.NewTemplateTitle', defaultMessage: 'Untitled Template'})
|
||||||
|
|
||||||
const view = createBoardView()
|
const view = createBoardView()
|
||||||
view.fields.viewType = 'board'
|
view.fields.viewType = 'board'
|
||||||
|
|
|
@ -100,7 +100,9 @@ const Workspace = React.memo((props: Props) => {
|
||||||
defaultMessage="You're editing a board template."
|
defaultMessage="You're editing a board template."
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>}
|
||||||
<CenterContent readonly={props.readonly}/>
|
<CenterContent
|
||||||
|
readonly={props.readonly}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
.WorkspaceSwitcherWrapper {
|
.WorkspaceSwitcherWrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
gap: 4px;
|
gap: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
|
@ -21,8 +21,22 @@
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-workspace-icon {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: rgb(var(--sidebar-text-rgb));
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.WorkspaceSwitcher {
|
.WorkspaceSwitcher {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -31,6 +45,8 @@
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
flex: 1;
|
||||||
|
max-width: 175px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
|
|
|
@ -7,6 +7,10 @@ import {useHistory} from 'react-router-dom'
|
||||||
|
|
||||||
import {IWorkspace} from '../../blocks/workspace'
|
import {IWorkspace} from '../../blocks/workspace'
|
||||||
import ChevronDown from '../../widgets/icons/chevronDown'
|
import ChevronDown from '../../widgets/icons/chevronDown'
|
||||||
|
import AddIcon from '../../widgets/icons/add'
|
||||||
|
import {setCurrent as setCurrentBoard} from '../../store/boards'
|
||||||
|
import {setCurrent as setCurrentView} from '../../store/views'
|
||||||
|
import {useAppDispatch} from '../../store/hooks'
|
||||||
|
|
||||||
import {UserSettings} from '../../userSettings'
|
import {UserSettings} from '../../userSettings'
|
||||||
|
|
||||||
|
@ -18,9 +22,18 @@ type Props = {
|
||||||
|
|
||||||
const WorkspaceSwitcher = (props: Props): JSX.Element => {
|
const WorkspaceSwitcher = (props: Props): JSX.Element => {
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
|
const {activeWorkspace} = props
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
const [showMenu, setShowMenu] = useState<boolean>(false)
|
const [showMenu, setShowMenu] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const goToEmptyCenterPanel = () => {
|
||||||
|
UserSettings.lastBoardId = null
|
||||||
|
UserSettings.lastViewId = null
|
||||||
|
dispatch(setCurrentBoard(''))
|
||||||
|
dispatch(setCurrentView(''))
|
||||||
|
history.replace(`/workspace/${activeWorkspace?.id}`)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'WorkspaceSwitcherWrapper'}>
|
<div className={'WorkspaceSwitcherWrapper'}>
|
||||||
<div
|
<div
|
||||||
|
@ -31,13 +44,13 @@ const WorkspaceSwitcher = (props: Props): JSX.Element => {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>{props.activeWorkspace?.title || DashboardOption.label}</span>
|
<span>{activeWorkspace?.title || DashboardOption.label}</span>
|
||||||
<ChevronDown/>
|
<ChevronDown/>
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
showMenu &&
|
showMenu &&
|
||||||
<WorkspaceOptions
|
<WorkspaceOptions
|
||||||
activeWorkspaceId={props.activeWorkspace?.id || DashboardOption.value}
|
activeWorkspaceId={activeWorkspace?.id || DashboardOption.value}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
setShowMenu(false)
|
setShowMenu(false)
|
||||||
}}
|
}}
|
||||||
|
@ -56,6 +69,14 @@ const WorkspaceSwitcher = (props: Props): JSX.Element => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
{activeWorkspace &&
|
||||||
|
<span
|
||||||
|
className='add-workspace-icon'
|
||||||
|
onClick={goToEmptyCenterPanel}
|
||||||
|
>
|
||||||
|
<AddIcon/>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import React, {useEffect, useState} from 'react'
|
import React, {useEffect, useState} from 'react'
|
||||||
import {batch} from 'react-redux'
|
import {batch} from 'react-redux'
|
||||||
import {FormattedMessage, useIntl} from 'react-intl'
|
import {FormattedMessage, useIntl} from 'react-intl'
|
||||||
import {generatePath, Redirect, useHistory, useRouteMatch} from 'react-router-dom'
|
import {generatePath, Redirect, useHistory, useRouteMatch, useLocation} from 'react-router-dom'
|
||||||
import {useHotkeys} from 'react-hotkeys-hook'
|
import {useHotkeys} from 'react-hotkeys-hook'
|
||||||
|
|
||||||
import {Block} from '../blocks/block'
|
import {Block} from '../blocks/block'
|
||||||
|
@ -47,6 +47,7 @@ const BoardPage = (props: Props): JSX.Element => {
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string, workspaceId?: string}>()
|
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string, workspaceId?: string}>()
|
||||||
const [websocketClosed, setWebsocketClosed] = useState(false)
|
const [websocketClosed, setWebsocketClosed] = useState(false)
|
||||||
|
const queryString = new URLSearchParams(useLocation().search)
|
||||||
const [mobileWarningClosed, setMobileWarningClosed] = useState(UserSettings.mobileWarningClosed)
|
const [mobileWarningClosed, setMobileWarningClosed] = useState(UserSettings.mobileWarningClosed)
|
||||||
|
|
||||||
let workspaceId = match.params.workspaceId || UserSettings.lastWorkspaceId || '0'
|
let workspaceId = match.params.workspaceId || UserSettings.lastWorkspaceId || '0'
|
||||||
|
@ -66,7 +67,6 @@ const BoardPage = (props: Props): JSX.Element => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Backward compatibility: This can be removed in the future, this is for
|
// Backward compatibility: This can be removed in the future, this is for
|
||||||
// transform the old query params into routes
|
// transform the old query params into routes
|
||||||
const queryString = new URLSearchParams(history.location.search)
|
|
||||||
const queryBoardId = queryString.get('id')
|
const queryBoardId = queryString.get('id')
|
||||||
const params = {...match.params}
|
const params = {...match.params}
|
||||||
let needsRedirect = false
|
let needsRedirect = false
|
||||||
|
@ -150,7 +150,6 @@ const BoardPage = (props: Props): JSX.Element => {
|
||||||
let token = localStorage.getItem('focalboardSessionId') || ''
|
let token = localStorage.getItem('focalboardSessionId') || ''
|
||||||
if (props.readonly) {
|
if (props.readonly) {
|
||||||
loadAction = initialReadOnlyLoad
|
loadAction = initialReadOnlyLoad
|
||||||
const queryString = new URLSearchParams(history.location.search)
|
|
||||||
token = token || queryString.get('r') || ''
|
token = token || queryString.get('r') || ''
|
||||||
}
|
}
|
||||||
dispatch(loadAction(match.params.boardId))
|
dispatch(loadAction(match.params.boardId))
|
||||||
|
@ -286,7 +285,9 @@ const BoardPage = (props: Props): JSX.Element => {
|
||||||
<div className='error'>
|
<div className='error'>
|
||||||
{intl.formatMessage({id: 'BoardPage.syncFailed', defaultMessage: 'Board may be deleted or access revoked.'})}
|
{intl.formatMessage({id: 'BoardPage.syncFailed', defaultMessage: 'Board may be deleted or access revoked.'})}
|
||||||
</div>}
|
</div>}
|
||||||
<Workspace readonly={props.readonly || false}/>
|
<Workspace
|
||||||
|
readonly={props.readonly || false}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user