Skip to content

Commit

Permalink
Merge pull request #1246 from research-software-directory/1212-softwa…
Browse files Browse the repository at this point in the history
…re-add-community-categories

Allow adding community categories to software
  • Loading branch information
ewan-escience authored Jul 11, 2024
2 parents dbaec1d + 41295e4 commit a91e6b3
Show file tree
Hide file tree
Showing 13 changed files with 436 additions and 66 deletions.
13 changes: 13 additions & 0 deletions database/109-category-functions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- SPDX-FileCopyrightText: 2024 Ewan Cahen (Netherlands eScience Center) <[email protected]>
-- SPDX-FileCopyrightText: 2024 Netherlands eScience Center
--
-- SPDX-License-Identifier: Apache-2.0

CREATE FUNCTION delete_community_categories_from_software(software_id UUID, community_id UUID)
RETURNS VOID
LANGUAGE sql AS
$$
DELETE FROM category_for_software
USING category
WHERE category_for_software.category_id = category.id AND category_for_software.software_id = software_id AND category.community = community_id;
$$;
2 changes: 1 addition & 1 deletion deployment/rsd/data/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,4 @@
"fontWeightBold": 600
}
}
}
}
6 changes: 5 additions & 1 deletion frontend/components/category/apiCategories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ import {CategoryEntry, CategoryID} from '~/types/Category'
import {getBaseUrl} from '~/utils/fetchHelpers'
import {TreeNode} from '~/types/TreeNode'

export async function loadCategoryRoots(community: string | null){
export async function loadCategoryRoots(community: string | null): Promise<TreeNode<CategoryEntry>[]> {

const communityFilter = community === null ? 'community=is.null' : `community=eq.${community}`

const resp = await fetch(`${getBaseUrl()}/category?${communityFilter}`)

if (!resp.ok) {
throw new Error(`${await resp.text()}`)
}

const categoriesArr: CategoryEntry[] = await resp.json()

return categoryEntriesToRoots(categoriesArr)
Expand Down
9 changes: 5 additions & 4 deletions frontend/components/category/useCategories.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2024 Ewan Cahen (Netherlands eScience Center) <[email protected]>
// SPDX-FileCopyrightText: 2024 Netherlands eScience Center
//
// SPDX-License-Identifier: Apache-2.0
Expand All @@ -16,22 +17,22 @@ export default function useCategories({community}:{community:string|null}){
const [loading, setLoading] = useState<boolean> (true)

useEffect(() => {
let abort = false
let abort: boolean = false
// only if there is community value
loadCategoryRoots(community)
.then(roots => {
if (abort===true) return
if (abort) return
setRoots(roots)
setError(null)
})
.catch(e => {
logger(`useCategories...${e.message}`,'error')
if (abort===true) return
if (abort) return
setError('Couldn\'t load the categories, please try again or contact us')
setRoots(null)
})
.finally(() => {
if (abort===true) return
if (abort) return
setLoading(false)
})

Expand Down
78 changes: 49 additions & 29 deletions frontend/components/software/TreeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,50 +21,70 @@ export type TreeSelectProps<T> = {
}

type RecursiveTreeSelectProps<T> = {
indent: number
textExtractor: (value: T) => string
keyExtractor: (value: T) => string
onSelect: (node: TreeNode<T>) => void
isSelected: (node: TreeNode<T>) => boolean
nodes: TreeNode<T>[]
}

export default function TreeSelect<T>({
export function RecursivelyGenerateItems<T>({
isSelected,
keyExtractor,
nodes,
onSelect,
roots,
textExtractor
}: Readonly<TreeSelectProps<T>>) {
function RecursivelyGenerateItems(propsRecursive: RecursiveTreeSelectProps<T>) {
return propsRecursive.nodes.map(node => {
const val = node.getValue()
if (val === null) {
return null
}

const key = keyExtractor(val)
const text = textExtractor(val)
if (node.childrenCount() === 0) {
return (
<MenuItem dense disableRipple key={key} onClick={() => onSelect(node)}>
<Checkbox disableRipple checked={isSelected(node)} />
<ListItemText primary={text} />
</MenuItem>
)
}
}: RecursiveTreeSelectProps<T>) {
return nodes.map(node => {
const val = node.getValue()
if (val === null) {
return null
}

const key = keyExtractor(val)
const text = textExtractor(val)
if (node.childrenCount() === 0) {
return (
<ListSubheader disableSticky key={key}>{text}
<ul>
<RecursivelyGenerateItems indent={propsRecursive.indent + 1} nodes={node.children()} />
</ul>
</ListSubheader>
<MenuItem dense disableRipple key={key} onClick={() => onSelect(node)}>
<Checkbox disableRipple checked={isSelected(node)} />
<ListItemText primary={text} />
</MenuItem>
)
})
}
}

return (
<ListSubheader disableSticky key={key}>{text}
<ul>
<RecursivelyGenerateItems
textExtractor={textExtractor}
onSelect={onSelect}
keyExtractor={keyExtractor}
isSelected={isSelected}
nodes={node.children()} />
</ul>
</ListSubheader>
)
})
}

export default function TreeSelect<T>({
isSelected,
keyExtractor,
onSelect,
roots,
textExtractor
}: Readonly<TreeSelectProps<T>>) {

return (
<FormControl fullWidth>
<InputLabel id="category-general-label">Select a category</InputLabel>
<Select labelId="category-general-label" label="Select a category">
<RecursivelyGenerateItems indent={0} nodes={roots}></RecursivelyGenerateItems>
<RecursivelyGenerateItems
textExtractor={textExtractor}
onSelect={onSelect}
keyExtractor={keyExtractor}
isSelected={isSelected}
nodes={roots} />
</Select>
</FormControl>
)
Expand Down
Loading

0 comments on commit a91e6b3

Please sign in to comment.