Skip to content

Commit

Permalink
WIP: all category levels select
Browse files Browse the repository at this point in the history
  • Loading branch information
dmijatovic committed Nov 19, 2024
1 parent 56376ac commit 3b31788
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 77 deletions.
88 changes: 16 additions & 72 deletions frontend/components/category/CategoriesDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
//
// 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'
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,
Expand All @@ -37,84 +36,22 @@ export default function CategoriesDialog({
const smallScreen = useMediaQuery('(max-width:600px)')
const [selectedCategoryIds, setSelectedCategoryIds] = useState<Set<string>>(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'){
setSelectedCategoryIds(selected)
}
},[selected,state])

function isSelected(node: TreeNode<CategoryEntry>) {
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<CategoryEntry>) {
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 (
<div className="flex-1 flex justify-center items-center">
<ContentLoader/>
</div>
)

case 'error':
return (
<Alert severity="error" sx={{marginTop: '0.5rem'}}>
{errorMsg ?? '500 - Unexpected error'}
</Alert>
)

case 'ready':
return (
<>
{(categories === null || categories.length === 0)
?
<Alert severity="info" sx={{'padding': '2rem'}}>
{noItemsMsg}
</Alert>
:
<RecursivelyGenerateItems
nodes={categories}
isSelected={isSelected}
keyExtractor={keyExtractor}
onSelect={onSelect}
textExtractor={textExtractor}
/>
}
</>
)
}
}

return (
<Dialog open fullScreen={smallScreen}>
<DialogTitle sx={{
Expand All @@ -131,7 +68,14 @@ export default function CategoriesDialog({
padding: '1rem 1.5rem 2.5rem !important',
}}>

{renderDialogContent()}
<CategoriesDialogBody
categories={categories}
state={state}
errorMsg={errorMsg}
noItemsMsg={noItemsMsg}
selectedCategoryIds={selectedCategoryIds}
setSelectedCategoryIds={setSelectedCategoryIds}
/>

</DialogContent>
<DialogActions sx={{
Expand Down
94 changes: 94 additions & 0 deletions frontend/components/category/CategoriesDialogBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2024 Netherlands eScience Center
//
// SPDX-License-Identifier: Apache-2.0

import Alert from '@mui/material/Alert'
import {CategoryEntry} from '~/types/Category'
import {TreeNode} from '~/types/TreeNode'
import ContentLoader from '../layout/ContentLoader'
import {CategoryList} from './CategoryList'
import List from '@mui/material/List'

type CategoriesDialogBodyProps={
categories: TreeNode<CategoryEntry>[],
state: 'loading' | 'error' | 'ready' | 'saving',
errorMsg: string | null
noItemsMsg: string
selectedCategoryIds: Set<string>,
setSelectedCategoryIds: (ids:Set<string>)=>void
}

export default function CategoriesDialogBody({
categories,state,errorMsg,noItemsMsg,
selectedCategoryIds, setSelectedCategoryIds
}:CategoriesDialogBodyProps) {


function isSelected(node: TreeNode<CategoryEntry>) {
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<CategoryEntry>) {
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 (
<div className="flex-1 flex justify-center items-center">
<ContentLoader/>
</div>
)

case 'error':
return (
<Alert severity="error" sx={{marginTop: '0.5rem'}}>
{errorMsg ?? '500 - Unexpected error'}
</Alert>
)

case 'ready':
return (
<>
{(categories === null || categories.length === 0)
?
<Alert severity="info" sx={{'padding': '2rem'}}>
{noItemsMsg}
</Alert>
:
<List>
<CategoryList
categories={categories}
isSelected={isSelected}
onSelect={onSelect}
/>
</List>
}
</>
)
}

}
127 changes: 127 additions & 0 deletions frontend/components/category/CategoryList.tsx
Original file line number Diff line number Diff line change
@@ -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<CategoryEntry>
onSelect: (node: TreeNode<CategoryEntry>) => void
isSelected: (node: TreeNode<CategoryEntry>) => boolean
}

function NodeWithChildren({
node,
isSelected,
onSelect
}:NodeWithChildrenProps){
// open/close children panel
const [open,setOpen] = useState(true)
// get category
const cat = node.getValue()

return (
<>
<ListItem
key={cat.id}
title={cat.short_name}
secondaryAction={
<IconButton
edge="end"
aria-label="expand"
onClick={()=>setOpen(!open)}
>
{open ? <ExpandLess /> : <ExpandMore />}
</IconButton>
}
disablePadding
dense
>
<ListItemButton onClick={() => onSelect(node)}>
<Checkbox disableRipple checked={isSelected(node)} />
<ListItemText
primary={cat.name}
// secondary={cat.name}
/>
</ListItemButton>
</ListItem>
{/* Children block */}
<Collapse
in={open}
timeout="auto"
unmountOnExit={false}
>
<List sx={{pl:'1rem'}}>
<CategoryList
categories={node.children()}
onSelect={onSelect}
isSelected={isSelected}
/>
</List>
</Collapse>
</>
)
}

export type CategoryListProps = {
onSelect: (node: TreeNode<CategoryEntry>) => void
isSelected: (node: TreeNode<CategoryEntry>) => boolean
categories: TreeNode<CategoryEntry>[]
}

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 (
<ListItem
key={cat.id}
// title={cat.name}
title={cat.short_name}
disablePadding
dense
>
<ListItemButton onClick={() => onSelect(node)}>
<Checkbox
disableRipple
checked={isSelected(node)}
/>
<ListItemText
primary={cat.name}
// secondary={cat.name}
/>
</ListItemButton>
</ListItem>
)
}

return (
<NodeWithChildren
key={cat.id}
node={node}
isSelected={isSelected}
onSelect={onSelect}
/>
)
})
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2024 Ewan Cahen (Netherlands eScience Center) <[email protected]>
// SPDX-FileCopyrightText: 2024 Netherlands eScience Center
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ export default function useSoftwareCategories({
const availableIds = new Set<string>()
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
Expand Down

0 comments on commit 3b31788

Please sign in to comment.