mirror of
https://github.com/mattermost/focalboard.git
synced 2025-03-17 20:37:54 +02:00
Hide board feature (#3526)
* Added logic to hide a board * WIP * Completed hide board implementation * Updated and added new tests * Lint fix * Updated snapshots
This commit is contained in:
parent
b6826f8509
commit
4ccb714d64
@ -150,6 +150,7 @@
|
||||
"GroupBy.hideEmptyGroups": "Hide {count} empty groups",
|
||||
"GroupBy.showHiddenGroups": "Show {count} hidden groups",
|
||||
"GroupBy.ungroup": "Ungroup",
|
||||
"HideBoard.MenuOption": "Hide board",
|
||||
"KanbanCard.untitled": "Untitled",
|
||||
"Mutator.new-board-from-template": "new board from template",
|
||||
"Mutator.new-card-from-template": "new card from template",
|
||||
|
@ -1,5 +1,214 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/sidebarSidebar dont show hidden boards 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Sidebar octo-sidebar"
|
||||
>
|
||||
<div
|
||||
class="octo-sidebar-header"
|
||||
>
|
||||
<div
|
||||
class="heading"
|
||||
>
|
||||
<div
|
||||
class="SidebarUserMenu"
|
||||
>
|
||||
<div
|
||||
class="ModalWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="logo"
|
||||
>
|
||||
<div
|
||||
class="logo-title"
|
||||
>
|
||||
<svg
|
||||
class="FocalboardLogoIcon Icon"
|
||||
version="1.1"
|
||||
viewBox="0 0 52.589677 64"
|
||||
x="0px"
|
||||
y="0px"
|
||||
>
|
||||
<path
|
||||
d="m 33.071077,12.069805 c -12.663,-3.4670001 -27.0530002,3.289 -31.6760002,16.943 -4.655,13.75 2.719,28.67 16.4690002,33.325 13.75,4.655 28.67,-2.719 33.326,-16.469 3.804,-11.235 -0.462,-22.701 -8.976,-29.249 l -0.46,4.871 h -0.001 c 4.631,4.896 6.709,11.941 4.325,18.985 -3.362,9.931 -14.447,15.151 -24.76,11.66 -10.313,-3.49 -15.9480002,-14.37 -12.5870002,-24.301 2.9750002,-8.788 11.9980002,-13.715 20.7430002,-12.625 v -10e-4 z m -6.175,16.488 c 3.456,-0.665 6.986,2.754 5.762,6.37 -0.854,2.522 -3.67,3.85 -6.291,2.962 -2.62,-0.887 -4.052,-3.651 -3.197,-6.174 0.573,-1.697 2.034,-2.852 3.726,-3.158 z m -1.285,-4.944 c -1.786,0.323 -3.45,1.104 -4.812,2.258 -1.299,1.101 -2.319,2.545 -2.898,4.258 -0.879,2.597 -0.579,5.323 0.617,7.632 1.206,2.329 3.325,4.234 6.07,5.164 2.744,0.929 5.584,0.701 7.959,-0.417 2.352,-1.107 4.246,-3.091 5.125,-5.688 0.555,-1.639 0.633,-3.254 0.344,-4.761 -0.21,-1.093 -0.615,-2.134 -1.174,-3.091 l 1.019,-5.107 c 0.189,0.187 0.374,0.378 0.552,0.574 1.75,1.919 3.008,4.283 3.508,6.877 0.415,2.154 0.304,4.457 -0.484,6.784 -1.239,3.661 -3.898,6.453 -7.193,8.005 -3.273,1.541 -7.175,1.858 -10.93,0.588 -3.754,-1.271 -6.661,-3.895 -8.326,-7.108 -1.674,-3.233 -2.09,-7.065 -0.851,-10.728 0.819,-2.419 2.26,-4.46 4.097,-6.016 1.88,-1.593 4.181,-2.673 6.656,-3.125 l -0.001,-0.004 c 1.759,-0.339 3.522,-0.313 5.213,0.016 l -3.583,3.761 c -0.294,0.028 -0.588,0.071 -0.883,0.127 h -0.025 z"
|
||||
/>
|
||||
<polygon
|
||||
points="26.057,32.594 37.495,11.658 36.79,8.44 41.066,0.207 43.683,4.611 48.803,4.434 44.185,12.48 40.902,13.697 29.542,34.491 "
|
||||
transform="translate(7.6780426e-5,-0.21919512)"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
Focalboard
|
||||
</span>
|
||||
<div
|
||||
class="versionFrame"
|
||||
>
|
||||
<div
|
||||
class="version"
|
||||
title="v7.3.0"
|
||||
>
|
||||
v7.3.0
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<div
|
||||
class="sidebarSwitcher"
|
||||
>
|
||||
<button
|
||||
class="IconButton"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
class="HideSidebarIcon Icon"
|
||||
viewBox="0 0 100 100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<polyline
|
||||
points="80,20 50,50 80,80"
|
||||
/>
|
||||
<polyline
|
||||
points="50,20 20,50, 50,80"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="WorkspaceTitle"
|
||||
/>
|
||||
<div
|
||||
class="BoardsSwitcherWrapper"
|
||||
>
|
||||
<div
|
||||
class="BoardsSwitcher"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-magnify MagnifyIcon"
|
||||
/>
|
||||
<div>
|
||||
<span>
|
||||
Find Boards
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="octo-sidebar-list"
|
||||
>
|
||||
<div
|
||||
class="SidebarCategory"
|
||||
>
|
||||
<div
|
||||
class="octo-sidebar-item category ' expanded "
|
||||
>
|
||||
<div
|
||||
class="octo-sidebar-title category-title"
|
||||
title="Category 1"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-chevron-down ChevronDownIcon"
|
||||
/>
|
||||
Category 1
|
||||
</div>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="IconButton"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-dots-horizontal OptionsIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="octo-sidebar-item subitem no-views"
|
||||
>
|
||||
No boards inside
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="SidebarCategory"
|
||||
>
|
||||
<div
|
||||
class="octo-sidebar-item category ' expanded "
|
||||
>
|
||||
<div
|
||||
class="octo-sidebar-title category-title"
|
||||
title="Boards"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-chevron-down ChevronDownIcon"
|
||||
/>
|
||||
Boards
|
||||
</div>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="IconButton"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-dots-horizontal OptionsIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="octo-sidebar-item subitem no-views"
|
||||
>
|
||||
No boards inside
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="octo-spacer"
|
||||
/>
|
||||
<div
|
||||
class="add-board"
|
||||
>
|
||||
+ Add board
|
||||
</div>
|
||||
<div
|
||||
class="SidebarSettingsMenu"
|
||||
>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="menu-entry"
|
||||
>
|
||||
Settings
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/sidebarSidebar sidebar expect hidden 1`] = `
|
||||
<div>
|
||||
<div
|
||||
|
@ -50,7 +50,10 @@ describe('components/sidebarSidebar', () => {
|
||||
views: [],
|
||||
},
|
||||
users: {
|
||||
me: {},
|
||||
me: {
|
||||
id: 'user_id_1',
|
||||
props: {},
|
||||
},
|
||||
},
|
||||
sidebar: {
|
||||
categoryAttributes: [
|
||||
@ -103,7 +106,10 @@ describe('components/sidebarSidebar', () => {
|
||||
views: [],
|
||||
},
|
||||
users: {
|
||||
me: {},
|
||||
me: {
|
||||
id: 'user_id_1',
|
||||
props: {},
|
||||
},
|
||||
},
|
||||
sidebar: {
|
||||
categoryAttributes: [
|
||||
@ -133,6 +139,61 @@ describe('components/sidebarSidebar', () => {
|
||||
customGlobal.innerWidth = 1024
|
||||
})
|
||||
|
||||
test('dont show hidden boards', () => {
|
||||
const store = mockStore({
|
||||
teams: {
|
||||
current: {id: 'team-id'},
|
||||
},
|
||||
boards: {
|
||||
current: board.id,
|
||||
boards: {
|
||||
[board.id]: board,
|
||||
},
|
||||
myBoardMemberships: {
|
||||
[board.id]: board,
|
||||
},
|
||||
},
|
||||
views: {
|
||||
views: [],
|
||||
},
|
||||
users: {
|
||||
me: {
|
||||
id: 'user_id_1',
|
||||
props: {
|
||||
hiddenBoardIDs: {
|
||||
[board.id]: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
sidebar: {
|
||||
categoryAttributes: [
|
||||
categoryAttribute1,
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const history = createMemoryHistory()
|
||||
|
||||
const component = wrapIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<Router history={history}>
|
||||
<Sidebar/>
|
||||
</Router>
|
||||
</ReduxProvider>,
|
||||
)
|
||||
const {container, getAllByText} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
|
||||
const sidebarBoards = container.getElementsByClassName('SidebarBoardItem')
|
||||
// The only board in redux store is hidden, so there should
|
||||
// be no boards visible in sidebar
|
||||
expect(sidebarBoards.length).toBe(0)
|
||||
|
||||
const noBoardsText = getAllByText('No boards inside')
|
||||
expect(noBoardsText.length).toBe(2) // one for custom category, one for default category
|
||||
})
|
||||
|
||||
// TODO: Fix this later
|
||||
// test('global templates', () => {
|
||||
// const store = mockStore({
|
||||
|
@ -31,6 +31,8 @@ import {getCurrentTeam} from '../../store/teams'
|
||||
|
||||
import {Constants} from "../../constants"
|
||||
|
||||
import {getMe} from "../../store/users"
|
||||
|
||||
import SidebarCategory from './sidebarCategory'
|
||||
import SidebarSettingsMenu from './sidebarSettingsMenu'
|
||||
import SidebarUserMenu from './sidebarUserMenu'
|
||||
@ -57,6 +59,8 @@ const Sidebar = (props: Props) => {
|
||||
const dispatch = useAppDispatch()
|
||||
const partialCategories = useAppSelector<Array<CategoryBoards>>(getSidebarCategories)
|
||||
const sidebarCategories = addMissingItems(partialCategories, boards)
|
||||
const me = useAppSelector(getMe)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
wsClient.addOnChange((_: WSClient, categories: Category[]) => {
|
||||
@ -107,6 +111,10 @@ const Sidebar = (props: Props) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (!me) {
|
||||
return <div/>
|
||||
}
|
||||
|
||||
if (isHidden) {
|
||||
return (
|
||||
<div className='Sidebar octo-sidebar hidden'>
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useCallback, useRef, useState} from 'react'
|
||||
import {useIntl} from 'react-intl'
|
||||
import {useHistory, useRouteMatch} from "react-router-dom"
|
||||
import {generatePath, useHistory, useRouteMatch} from "react-router-dom"
|
||||
|
||||
import {Board} from '../../blocks/board'
|
||||
import {BoardView, IViewType} from '../../blocks/boardView'
|
||||
@ -17,7 +17,7 @@ import BoardPermissionGate from '../permissions/boardPermissionGate'
|
||||
import './sidebarBoardItem.scss'
|
||||
import {CategoryBoards} from '../../store/sidebar'
|
||||
import CreateNewFolder from '../../widgets/icons/newFolder'
|
||||
import {useAppSelector} from '../../store/hooks'
|
||||
import {useAppDispatch, useAppSelector} from '../../store/hooks'
|
||||
import {getCurrentBoardViews, getCurrentViewId} from '../../store/views'
|
||||
import Folder from '../../widgets/icons/folder'
|
||||
import Check from '../../widgets/icons/checkIcon'
|
||||
@ -33,6 +33,12 @@ import DuplicateIcon from "../../widgets/icons/duplicate"
|
||||
import {Utils} from "../../utils"
|
||||
|
||||
import AddIcon from "../../widgets/icons/add"
|
||||
import CloseIcon from "../../widgets/icons/close"
|
||||
import {UserConfigPatch} from "../../user"
|
||||
import {getMe, patchProps} from "../../store/users"
|
||||
import octoClient from "../../octoClient"
|
||||
import {getCurrentBoardId, getMySortedBoards} from "../../store/boards"
|
||||
import {UserSettings} from "../../userSettings"
|
||||
|
||||
const iconForViewType = (viewType: IViewType): JSX.Element => {
|
||||
switch (viewType) {
|
||||
@ -63,9 +69,13 @@ const SidebarBoardItem = (props: Props) => {
|
||||
const boardViews = useAppSelector(getCurrentBoardViews)
|
||||
const currentViewId = useAppSelector(getCurrentViewId)
|
||||
const teamID = team?.id || ''
|
||||
const me = useAppSelector(getMe)
|
||||
|
||||
const match = useRouteMatch<{boardId: string, viewId?: string, cardId?: string, teamId?: string}>()
|
||||
const history = useHistory()
|
||||
const dispatch = useAppDispatch()
|
||||
const myAllBoards = useAppSelector(getMySortedBoards)
|
||||
const currentBoardID = useAppSelector(getCurrentBoardId)
|
||||
|
||||
const generateMoveToCategoryOptions = (boardID: string) => {
|
||||
return props.allCategories.map((category) => (
|
||||
@ -107,6 +117,67 @@ const SidebarBoardItem = (props: Props) => {
|
||||
|
||||
}, [board.id])
|
||||
|
||||
const showTemplatePicker = () => {
|
||||
// if the same board, reuse the match params
|
||||
// otherwise remove viewId and cardId, results in first view being selected
|
||||
const params = {teamId: match.params.teamId}
|
||||
const newPath = generatePath('/team/:teamId?', params)
|
||||
history.push(newPath)
|
||||
}
|
||||
|
||||
const handleHideBoard = async() => {
|
||||
console.log('handleHideBoard')
|
||||
if (!me ) {
|
||||
return
|
||||
}
|
||||
|
||||
// creating new array from me.props.hiddenBoardIDs as
|
||||
// me.props.hiddenBoardIDs belongs to Redux state and
|
||||
// so is immutable.
|
||||
const hiddenBoards = {...(me.props.hiddenBoardIDs || {})}
|
||||
|
||||
// check for already hidden board. Skip if so
|
||||
// if (hiddenBoards.indexOf(board.id) > -1) {
|
||||
// return
|
||||
// }
|
||||
|
||||
hiddenBoards[board.id] = true
|
||||
const hiddenBoardsArray = Object.keys(hiddenBoards)
|
||||
const patch: UserConfigPatch = {
|
||||
updatedFields: {
|
||||
'hiddenBoardIDs': JSON.stringify(hiddenBoardsArray),
|
||||
}
|
||||
}
|
||||
const patchedProps = await octoClient.patchUserConfig(me.id, patch)
|
||||
if (!patchedProps) {
|
||||
return
|
||||
}
|
||||
|
||||
await dispatch(patchProps(patchedProps))
|
||||
|
||||
// If we're hiding the board we're currently on,
|
||||
// we need to switch to a different board once its hidden.
|
||||
if (currentBoardID === props.board.id) {
|
||||
// There's no special logic on what the next board needs to be.
|
||||
// To keep things simple, we just switch to the first unhidden board
|
||||
|
||||
// Empty board ID navigates to template picker, which is
|
||||
// fine if there are no more visible boards to switch to.
|
||||
const visibleBoards = myAllBoards.filter((b) => !hiddenBoards[b.id])
|
||||
|
||||
if (visibleBoards.length === 0) {
|
||||
UserSettings.setLastBoardID(match.params.teamId!, null)
|
||||
showTemplatePicker()
|
||||
} else {
|
||||
let nextBoardID = ''
|
||||
if (visibleBoards.length > 0) {
|
||||
nextBoardID = visibleBoards[0].id
|
||||
}
|
||||
props.showBoard(nextBoardID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const boardItemRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const title = board.title || intl.formatMessage({id: 'Sidebar.untitled-board', defaultMessage: '(Untitled Board)'})
|
||||
@ -179,6 +250,12 @@ const SidebarBoardItem = (props: Props) => {
|
||||
icon={<AddIcon/>}
|
||||
onClick={() => handleDuplicateBoard(true)}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='hideBoard'
|
||||
name={intl.formatMessage({id: 'HideBoard.MenuOption', defaultMessage: 'Hide board'})}
|
||||
icon={<CloseIcon/>}
|
||||
onClick={() => handleHideBoard()}
|
||||
/>
|
||||
</Menu>
|
||||
</MenuWrapper>
|
||||
</div>
|
||||
|
@ -48,6 +48,7 @@ describe('components/sidebarCategory', () => {
|
||||
users: {
|
||||
me: {
|
||||
id: 'user_id_1',
|
||||
props: {}
|
||||
},
|
||||
},
|
||||
boards: {
|
||||
|
@ -78,7 +78,19 @@ const SidebarCategory = (props: Props) => {
|
||||
props.hideSidebar()
|
||||
}, [match, history])
|
||||
|
||||
const isBoardVisible = (boardID: string): boolean => {
|
||||
// hide if board doesn't belong to current category
|
||||
if (!blocks.includes(boardID)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// hide if board was hidden by the user
|
||||
const hiddenBoardIDs = me?.props.hiddenBoardIDs || {}
|
||||
return !hiddenBoardIDs[boardID]
|
||||
}
|
||||
|
||||
const blocks = props.categoryBoards.boardIDs || []
|
||||
const visibleBlocks = props.categoryBoards.boardIDs.filter((boardID) => isBoardVisible(boardID))
|
||||
|
||||
const handleCreateNewCategory = () => {
|
||||
setShowCreateCategoryModal(true)
|
||||
@ -189,7 +201,7 @@ const SidebarCategory = (props: Props) => {
|
||||
</Menu>
|
||||
</MenuWrapper>
|
||||
</div>
|
||||
{!collapsed && blocks.length === 0 &&
|
||||
{!collapsed && visibleBlocks.length === 0 &&
|
||||
<div className='octo-sidebar-item subitem no-views'>
|
||||
<FormattedMessage
|
||||
id='Sidebar.no-boards-in-category'
|
||||
@ -197,7 +209,7 @@ const SidebarCategory = (props: Props) => {
|
||||
/>
|
||||
</div>}
|
||||
{!collapsed && props.boards.map((board: Board) => {
|
||||
if (!blocks.includes(board.id)) {
|
||||
if (!isBoardVisible(board.id)) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
|
@ -21,10 +21,13 @@ import wsClient, {WSClient} from '../wsclient'
|
||||
import {ClientConfig} from '../config/clientConfig'
|
||||
import {Utils} from '../utils'
|
||||
|
||||
import {getMe} from "../store/users"
|
||||
|
||||
import CenterPanel from './centerPanel'
|
||||
import BoardTemplateSelector from './boardTemplateSelector/boardTemplateSelector'
|
||||
|
||||
import Sidebar from './sidebar/sidebar'
|
||||
|
||||
import './workspace.scss'
|
||||
|
||||
type Props = {
|
||||
@ -46,6 +49,12 @@ function CenterContent(props: Props) {
|
||||
const cardLimitTimestamp = useAppSelector(getCardLimitTimestamp)
|
||||
const history = useHistory()
|
||||
const dispatch = useAppDispatch()
|
||||
const me = useAppSelector(getMe)
|
||||
|
||||
const isBoardHidden = () => {
|
||||
const hiddenBoardIDs = me?.props.hiddenBoardIDs || {}
|
||||
return hiddenBoardIDs[board.id]
|
||||
}
|
||||
|
||||
const showCard = useCallback((cardId?: string) => {
|
||||
const params = {...match.params, cardId}
|
||||
@ -76,7 +85,7 @@ function CenterContent(props: Props) {
|
||||
}
|
||||
}, [cardLimitTimestamp, match.params.boardId, templates])
|
||||
|
||||
if (board && activeView) {
|
||||
if (board && !isBoardHidden() && activeView) {
|
||||
let property = groupByProperty
|
||||
if ((!property || property.type !== 'select') && activeView.fields.viewType === 'board') {
|
||||
property = board?.cardProperties.find((o) => o.type === 'select')
|
||||
@ -104,7 +113,7 @@ function CenterContent(props: Props) {
|
||||
)
|
||||
}
|
||||
|
||||
if (board || isLoading) {
|
||||
if ((board && !isBoardHidden()) || isLoading) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import octoClient from '../../octoClient'
|
||||
import {Subscription, WSClient} from '../../wsclient'
|
||||
import {Utils} from '../../utils'
|
||||
import {useWebsockets} from '../../hooks/websockets'
|
||||
import {IUser} from '../../user'
|
||||
import {IUser, UserConfigPatch} from '../../user'
|
||||
import {Block} from '../../blocks/block'
|
||||
import {ContentBlock} from '../../blocks/contentBlock'
|
||||
import {CommentBlock} from '../../blocks/commentBlock'
|
||||
@ -37,7 +37,7 @@ import {
|
||||
fetchUserBlockSubscriptions,
|
||||
getMe,
|
||||
followBlock,
|
||||
unfollowBlock,
|
||||
unfollowBlock, patchProps,
|
||||
} from '../../store/users'
|
||||
import {setGlobalError} from '../../store/globalError'
|
||||
import {UserSettings} from '../../userSettings'
|
||||
@ -201,6 +201,41 @@ const BoardPage = (props: Props): JSX.Element => {
|
||||
}
|
||||
}, [teamId, match.params.boardId, viewId, me?.id])
|
||||
|
||||
const handleUnhideBoard = async (boardID: string) => {
|
||||
console.log(`handleUnhideBoard called`)
|
||||
if (!me) {
|
||||
return
|
||||
}
|
||||
|
||||
const hiddenBoards = {...(me.props.hiddenBoardIDs || {})}
|
||||
// const index = hiddenBoards.indexOf(boardID)
|
||||
// hiddenBoards.splice(index, 1)
|
||||
delete hiddenBoards[boardID]
|
||||
const hiddenBoardsArray = Object.keys(hiddenBoards)
|
||||
const patch: UserConfigPatch = {
|
||||
updatedFields: {
|
||||
'hiddenBoardIDs': JSON.stringify(hiddenBoardsArray),
|
||||
}
|
||||
}
|
||||
const patchedProps = await octoClient.patchUserConfig(me.id, patch)
|
||||
if (!patchedProps) {
|
||||
return
|
||||
}
|
||||
|
||||
await dispatch(patchProps(patchedProps))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!teamId || !match.params.boardId) {
|
||||
return
|
||||
}
|
||||
|
||||
const hiddenBoardIDs = me?.props.hiddenBoardIDs || {}
|
||||
if (hiddenBoardIDs[match.params.boardId]) {
|
||||
handleUnhideBoard(match.params.boardId)
|
||||
}
|
||||
}, [me?.id, teamId, match.params.boardId])
|
||||
|
||||
if (props.readonly) {
|
||||
useEffect(() => {
|
||||
if (activeBoardId && activeViewId) {
|
||||
|
@ -4,7 +4,7 @@
|
||||
import {createSlice, createAsyncThunk, PayloadAction, createSelector} from '@reduxjs/toolkit'
|
||||
|
||||
import {default as client} from '../octoClient'
|
||||
import {IUser} from '../user'
|
||||
import {IUser, parseUserProps} from '../user'
|
||||
|
||||
import {Utils} from '../utils'
|
||||
|
||||
@ -47,6 +47,9 @@ const usersSlice = createSlice({
|
||||
reducers: {
|
||||
setMe: (state, action: PayloadAction<IUser|null>) => {
|
||||
state.me = action.payload
|
||||
if (state.me) {
|
||||
state.me.props = parseUserProps(state.me.props)
|
||||
}
|
||||
state.loggedIn = Boolean(state.me)
|
||||
},
|
||||
setBoardUsers: (state, action: PayloadAction<IUser[]>) => {
|
||||
@ -74,13 +77,16 @@ const usersSlice = createSlice({
|
||||
},
|
||||
patchProps: (state, action: PayloadAction<Record<string, string>>) => {
|
||||
if (state.me) {
|
||||
state.me.props = action.payload
|
||||
state.me.props = parseUserProps(action.payload)
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(fetchMe.fulfilled, (state, action) => {
|
||||
state.me = action.payload || null
|
||||
if (state.me) {
|
||||
state.me.props = parseUserProps(state.me.props)
|
||||
}
|
||||
state.loggedIn = Boolean(state.me)
|
||||
})
|
||||
builder.addCase(fetchMe.rejected, (state) => {
|
||||
|
@ -27,6 +27,20 @@ interface UserConfigPatch {
|
||||
deletedFields?: string[]
|
||||
}
|
||||
|
||||
function parseUserProps(props: Record<string, any>): Record<string, any> {
|
||||
const processedProps = props
|
||||
const hiddenBoardIDs = props.hiddenBoardIDs ? JSON.parse(props.hiddenBoardIDs) : []
|
||||
processedProps.hiddenBoardIDs = {}
|
||||
hiddenBoardIDs.forEach((boardID: string) => processedProps.hiddenBoardIDs[boardID] = true)
|
||||
return processedProps
|
||||
}
|
||||
|
||||
const UserPropPrefix = 'focalboard_'
|
||||
|
||||
export {IUser, UserWorkspace, UserConfigPatch, UserPropPrefix}
|
||||
export {
|
||||
IUser,
|
||||
UserWorkspace,
|
||||
UserConfigPatch,
|
||||
UserPropPrefix,
|
||||
parseUserProps,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user