From 3b3178801eff19ca93dabec363d651e430d19cfe Mon Sep 17 00:00:00 2001 From: "Dusan Mijatovic (PC2020)" Date: Tue, 19 Nov 2024 18:14:00 +0100 Subject: [PATCH] WIP: all category levels select --- .../components/category/CategoriesDialog.tsx | 88 +++--------- .../category/CategoriesDialogBody.tsx | 94 +++++++++++++ frontend/components/category/CategoryList.tsx | 127 ++++++++++++++++++ .../{software => category}/TreeSelect.tsx | 1 + .../CommunityAddCategoriesDialog.tsx | 2 +- .../edit/links/AutosaveSoftwareCategories.tsx | 2 +- .../organisations/useSoftwareCategories.tsx | 6 +- 7 files changed, 243 insertions(+), 77 deletions(-) create mode 100644 frontend/components/category/CategoriesDialogBody.tsx create mode 100644 frontend/components/category/CategoryList.tsx rename frontend/components/{software => category}/TreeSelect.tsx (96%) diff --git a/frontend/components/category/CategoriesDialog.tsx b/frontend/components/category/CategoriesDialog.tsx index 40bac32ad..d7cdd3af9 100644 --- a/frontend/components/category/CategoriesDialog.tsx +++ b/frontend/components/category/CategoriesDialog.tsx @@ -3,10 +3,11 @@ // // SPDX-License-Identifier: Apache-2.0 +import {useEffect, useState} from 'react' + import Dialog from '@mui/material/Dialog' import DialogContent from '@mui/material/DialogContent' import DialogTitle from '@mui/material/DialogTitle' -import Alert from '@mui/material/Alert' import DialogActions from '@mui/material/DialogActions' import Button from '@mui/material/Button' import useMediaQuery from '@mui/material/useMediaQuery' @@ -14,9 +15,7 @@ import SaveIcon from '@mui/icons-material/Save' import {TreeNode} from '~/types/TreeNode' import {CategoryEntry} from '~/types/Category' -import ContentLoader from '../layout/ContentLoader' -import {RecursivelyGenerateItems} from '~/components/software/TreeSelect' -import {useEffect, useState} from 'react' +import CategoriesDialogBody from './CategoriesDialogBody' type CategoriesDialogProps={ title: string, @@ -37,11 +36,11 @@ export default function CategoriesDialog({ const smallScreen = useMediaQuery('(max-width:600px)') const [selectedCategoryIds, setSelectedCategoryIds] = useState>(new Set()) - // console.group('CategoriesDialog') - // console.log('state...', state) - // console.log('selected...', selected) - // console.log('selectedCategoryIds...',selectedCategoryIds) - // console.groupEnd() + console.group('CategoriesDialog') + console.log('state...', state) + console.log('selected...', selected) + console.log('selectedCategoryIds...',selectedCategoryIds) + console.groupEnd() useEffect(()=>{ if (state==='ready'){ @@ -49,72 +48,10 @@ export default function CategoriesDialog({ } },[selected,state]) - function isSelected(node: TreeNode) { - const val = node.getValue() - return selectedCategoryIds.has(val.id) - } - - function textExtractor(value: CategoryEntry) { - return value.name - } - - function keyExtractor(value: CategoryEntry) { - return value.id - } - - function onSelect(node: TreeNode) { - const val = node.getValue() - if (selectedCategoryIds.has(val.id)) { - selectedCategoryIds.delete(val.id) - } else { - selectedCategoryIds.add(val.id) - } - setSelectedCategoryIds(new Set(selectedCategoryIds)) - } - function isSaveDisabled(){ return categories === null || categories.length === 0 || state !== 'ready' } - function renderDialogContent(): JSX.Element { - switch (state) { - case 'loading': - case 'saving': - return ( -
- -
- ) - - case 'error': - return ( - - {errorMsg ?? '500 - Unexpected error'} - - ) - - case 'ready': - return ( - <> - {(categories === null || categories.length === 0) - ? - - {noItemsMsg} - - : - - } - - ) - } - } - return ( - {renderDialogContent()} + [], + state: 'loading' | 'error' | 'ready' | 'saving', + errorMsg: string | null + noItemsMsg: string + selectedCategoryIds: Set, + setSelectedCategoryIds: (ids:Set)=>void +} + +export default function CategoriesDialogBody({ + categories,state,errorMsg,noItemsMsg, + selectedCategoryIds, setSelectedCategoryIds +}:CategoriesDialogBodyProps) { + + + function isSelected(node: TreeNode) { + const val = node.getValue() + // default approach + // return selectedCategoryIds.has(val.id) + + // ALTERNATIVE APPROACH + // directly selected + if (selectedCategoryIds.has(val.id)) return true + + // any of children selected? + const found = node.children().find(item=>{ + return isSelected(item) + }) + // debugger + if (found) return true + // none of children selected either + return false + } + + function onSelect(node: TreeNode) { + const val = node.getValue() + if (selectedCategoryIds.has(val.id)) { + selectedCategoryIds.delete(val.id) + } else { + selectedCategoryIds.add(val.id) + } + setSelectedCategoryIds(new Set(selectedCategoryIds)) + } + + switch (state) { + case 'loading': + case 'saving': + return ( +
+ +
+ ) + + case 'error': + return ( + + {errorMsg ?? '500 - Unexpected error'} + + ) + + case 'ready': + return ( + <> + {(categories === null || categories.length === 0) + ? + + {noItemsMsg} + + : + + + + } + + ) + } + +} diff --git a/frontend/components/category/CategoryList.tsx b/frontend/components/category/CategoryList.tsx new file mode 100644 index 000000000..f64599e80 --- /dev/null +++ b/frontend/components/category/CategoryList.tsx @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import {useState} from 'react' +import ExpandLess from '@mui/icons-material/ExpandLess' +import ExpandMore from '@mui/icons-material/ExpandMore' +import Checkbox from '@mui/material/Checkbox' +import Collapse from '@mui/material/Collapse' +import IconButton from '@mui/material/IconButton' +import List from '@mui/material/List' +import ListItem from '@mui/material/ListItem' +import ListItemButton from '@mui/material/ListItemButton' +import ListItemText from '@mui/material/ListItemText' + +import {TreeNode} from '~/types/TreeNode' +import {CategoryEntry} from '~/types/Category' + +type NodeWithChildrenProps = { + node: TreeNode + onSelect: (node: TreeNode) => void + isSelected: (node: TreeNode) => boolean +} + +function NodeWithChildren({ + node, + isSelected, + onSelect +}:NodeWithChildrenProps){ + // open/close children panel + const [open,setOpen] = useState(true) + // get category + const cat = node.getValue() + + return ( + <> + setOpen(!open)} + > + {open ? : } + + } + disablePadding + dense + > + onSelect(node)}> + + + + + {/* Children block */} + + + + + + + ) +} + +export type CategoryListProps = { + onSelect: (node: TreeNode) => void + isSelected: (node: TreeNode) => boolean + categories: TreeNode[] +} + +export function CategoryList({ + categories, + isSelected, + onSelect +}: CategoryListProps) { + // loop all categories + return categories.map(node => { + const cat = node.getValue() + + // single cat element without children + if (node.childrenCount() === 0) { + return ( + + onSelect(node)}> + + + + + ) + } + + return ( + + ) + }) +} diff --git a/frontend/components/software/TreeSelect.tsx b/frontend/components/category/TreeSelect.tsx similarity index 96% rename from frontend/components/software/TreeSelect.tsx rename to frontend/components/category/TreeSelect.tsx index 0b1acd767..35c29e70f 100644 --- a/frontend/components/software/TreeSelect.tsx +++ b/frontend/components/category/TreeSelect.tsx @@ -1,3 +1,4 @@ +// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center) // SPDX-FileCopyrightText: 2024 Ewan Cahen (Netherlands eScience Center) // SPDX-FileCopyrightText: 2024 Netherlands eScience Center // diff --git a/frontend/components/software/edit/communities/CommunityAddCategoriesDialog.tsx b/frontend/components/software/edit/communities/CommunityAddCategoriesDialog.tsx index 1e7e48e22..31ee3d68e 100644 --- a/frontend/components/software/edit/communities/CommunityAddCategoriesDialog.tsx +++ b/frontend/components/software/edit/communities/CommunityAddCategoriesDialog.tsx @@ -13,7 +13,7 @@ import {CategoryEntry} from '~/types/Category' import ContentLoader from '~/components/layout/ContentLoader' import Alert from '@mui/material/Alert' import {loadCategoryRoots} from '~/components/category/apiCategories' -import {RecursivelyGenerateItems} from '~/components/software/TreeSelect' +import {RecursivelyGenerateItems} from '~/components/category/TreeSelect' import {CategoryForSoftwareIds} from '~/types/SoftwareTypes' import DialogActions from '@mui/material/DialogActions' import Button from '@mui/material/Button' diff --git a/frontend/components/software/edit/links/AutosaveSoftwareCategories.tsx b/frontend/components/software/edit/links/AutosaveSoftwareCategories.tsx index e74d489e2..e0c40694a 100644 --- a/frontend/components/software/edit/links/AutosaveSoftwareCategories.tsx +++ b/frontend/components/software/edit/links/AutosaveSoftwareCategories.tsx @@ -9,7 +9,7 @@ import {Fragment, useMemo, useState} from 'react' import {CategoryEntry} from '~/types/Category' import {categoryTreeNodesSort, ReorderedCategories} from '~/utils/categories' -import TreeSelect from '~/components/software/TreeSelect' +import TreeSelect from '~/components/category/TreeSelect' import {TreeNode} from '~/types/TreeNode' import {addCategoryToSoftware, deleteCategoryToSoftware} from '~/utils/getSoftware' import {useSession} from '~/auth' diff --git a/frontend/components/software/edit/organisations/useSoftwareCategories.tsx b/frontend/components/software/edit/organisations/useSoftwareCategories.tsx index e484c1f11..a66a66386 100644 --- a/frontend/components/software/edit/organisations/useSoftwareCategories.tsx +++ b/frontend/components/software/edit/organisations/useSoftwareCategories.tsx @@ -47,9 +47,9 @@ export default function useSoftwareCategories({ const availableIds = new Set() categories.forEach(root=>{ root.forEach(node=>{ - if (node.children().length === 0) { - availableIds.add(node.getValue().id) - } + // if (node.children().length === 0) { + availableIds.add(node.getValue().id) + // } }) }) if (abort) return