From 004f6c1dd799451e84bd1363ce1439a168f60222 Mon Sep 17 00:00:00 2001 From: "Dusan Mijatovic (PC2020)" Date: Mon, 3 Feb 2025 15:42:52 +0100 Subject: [PATCH] feat: use chip layout for custom categories on software page refactor: show modal after loading to avoid form resizing --- .../components/category/CategoriesDialog.tsx | 9 ++-- .../category/CategoriesDialogBody.tsx | 6 +-- .../category/CategoriesWithHeadlines.tsx | 33 ------------ frontend/components/category/CategoryIcon.tsx | 33 ++++++++++++ frontend/components/category/CategoryTree.tsx | 52 +++++++++++-------- .../components/category/apiCategories.test.ts | 17 +++--- frontend/components/layout/TagChipFilter.tsx | 4 +- .../EditProjectOrganisationsIndex.test.tsx | 2 +- .../components/software/AboutLanguages.tsx | 10 ++-- frontend/components/software/AboutLicense.tsx | 10 ++-- .../software/AboutPackageManagers.tsx | 6 +-- frontend/components/software/AboutSection.tsx | 43 +++++++-------- .../components/software/AboutSourceCode.tsx | 6 +-- .../components/software/CategoriesSidebar.tsx | 41 +++++++++++++++ .../components/software/SoftwareKeywords.tsx | 10 ++-- .../EditSoftwareOrganisationsIndex.test.tsx | 2 +- .../SortableOrganisationItem.tsx | 2 +- .../SortableOrganisationsList.tsx | 6 +-- frontend/components/typography/Icon.tsx | 39 -------------- .../components/typography/SidebarHeadline.tsx | 17 ------ frontend/types/Category.ts | 4 +- 21 files changed, 175 insertions(+), 177 deletions(-) delete mode 100644 frontend/components/category/CategoriesWithHeadlines.tsx create mode 100644 frontend/components/category/CategoryIcon.tsx create mode 100644 frontend/components/software/CategoriesSidebar.tsx delete mode 100644 frontend/components/typography/Icon.tsx delete mode 100644 frontend/components/typography/SidebarHeadline.tsx diff --git a/frontend/components/category/CategoriesDialog.tsx b/frontend/components/category/CategoriesDialog.tsx index d43b2d5f4..5aa5c6184 100644 --- a/frontend/components/category/CategoriesDialog.tsx +++ b/frontend/components/category/CategoriesDialog.tsx @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2024 Netherlands eScience Center +// SPDX-FileCopyrightText: 2024 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -53,7 +53,10 @@ export default function CategoriesDialog({ } return ( - + [], diff --git a/frontend/components/category/CategoriesWithHeadlines.tsx b/frontend/components/category/CategoriesWithHeadlines.tsx deleted file mode 100644 index c0c6c996a..000000000 --- a/frontend/components/category/CategoriesWithHeadlines.tsx +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Felix Mühlbauer (GFZ) -// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences -// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2024 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2024 Netherlands eScience Center -// -// SPDX-License-Identifier: Apache-2.0 - -import React from 'react' -import {CategoryPath} from '~/types/Category' -import {SidebarHeadline} from '~/components/typography/SidebarHeadline' -import {CategoryTreeLevel} from '~/components/category/CategoryTree' -import {useCategoryTree} from './useCategoryTree' - -type CategoriesWithHeadlinesProps = { - categories: CategoryPath[] -} - -export const CategoriesWithHeadlines = ({categories}: CategoriesWithHeadlinesProps) => { - const tree = useCategoryTree(categories) - - return tree.map(node => { - const category = node.getValue() - const children = node.children() - - return - -
- -
-
- }) -} diff --git a/frontend/components/category/CategoryIcon.tsx b/frontend/components/category/CategoryIcon.tsx new file mode 100644 index 000000000..d252247c9 --- /dev/null +++ b/frontend/components/category/CategoryIcon.tsx @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2025 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import MuiCategoryIcon from '@mui/icons-material/Category' +import MuiQuestionMarkIcon from '@mui/icons-material/QuestionMark' +import MuiScienceIcon from '@mui/icons-material/Science' + +/** + * Use lower case name of the icon import. For example '@mui/icons-material/QuestionMark' name is questionmark. + * Additional icons can be added here by importing them from library and extending MuiIconName type. + */ +export type MuiIconName = 'questionmark'|'category'|'science' | 'none' + +export default function CategoryIcon({name}:Readonly<{name?:MuiIconName}>) { + // default is category icon + if (!name) return + + // select based on name + switch(name.toLocaleLowerCase()){ + case 'category': + return + case 'science': + return + case 'questionmark': + return + case 'none': + return null + default: + return + } +} diff --git a/frontend/components/category/CategoryTree.tsx b/frontend/components/category/CategoryTree.tsx index 29e35e3f4..b414bf72d 100644 --- a/frontend/components/category/CategoryTree.tsx +++ b/frontend/components/category/CategoryTree.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: 2023 - 2024 Felix Mühlbauer (GFZ) // SPDX-FileCopyrightText: 2023 - 2024 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences -// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 - 2025 Netherlands eScience Center // SPDX-FileCopyrightText: 2024 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2024 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -35,25 +35,31 @@ type TreeLevelProps = { onRemoveHandler? : (event: React.MouseEvent) => void } const TreeLevel = ({items, showLongNames, onRemoveHandler}: TreeLevelProps) => { - return
    - {items.map((item) => { - const category = item.getValue() - - const children = item.children() - return ( -
  • -
    - - {showLongNames ? category.name : category.short_name} - - {onRemoveHandler && children.length === 0 && - } -
    - {children.length > 0 && - } -
  • - ) - })} -
+ return ( +
    + {items.map((item) => { + const category = item.getValue() + const children = item.children() + return ( +
  • +
    + + {showLongNames ? category.name : category.short_name} + + { onRemoveHandler && children.length === 0 ? + + + + :null + } +
    + {children.length > 0 ? + + : null + } +
  • + ) + })} +
+ ) } diff --git a/frontend/components/category/apiCategories.test.ts b/frontend/components/category/apiCategories.test.ts index e29e1ca83..c952b8fd0 100644 --- a/frontend/components/category/apiCategories.test.ts +++ b/frontend/components/category/apiCategories.test.ts @@ -1,5 +1,6 @@ +// SPDX-FileCopyrightText: 2024 - 2025 Netherlands eScience Center // SPDX-FileCopyrightText: 2024 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2024 Netherlands eScience Center +// SPDX-FileCopyrightText: 2025 Dusan Mijatovic (Netherlands eScience Center) // // SPDX-License-Identifier: Apache-2.0 @@ -9,13 +10,13 @@ import {TreeNode} from '~/types/TreeNode' import {shuffle} from '~/utils/jest/utils' it('generates the category tree correctly', () => { - const grandChild1: CategoryEntry = {id: 'grandChild1', parent: 'child1', short_name: '', name: '', community: null, provenance_iri: null, properties: {}} - const grandChild2: CategoryEntry = {id: 'grandChild2', parent: 'child1', short_name: '', name: '', community: null, provenance_iri: null, properties: {}} - const grandChild3: CategoryEntry = {id: 'grandChild3', parent: 'child2', short_name: '', name: '', community: null, provenance_iri: null, properties: {}} - const grandChild4: CategoryEntry = {id: 'grandChild4', parent: 'child2', short_name: '', name: '', community: null, provenance_iri: null, properties: {}} - const child1: CategoryEntry = {id: 'child1', parent: 'parent', short_name: '', name: '', community: null, provenance_iri: null, properties: {}} - const child2: CategoryEntry = {id: 'child2', parent: 'parent', short_name: '', name: '', community: null, provenance_iri: null, properties: {}} - const parent: CategoryEntry = {id: 'parent', parent: null, short_name: '', name: '', community: null, provenance_iri: null, properties: {}} + const grandChild1: CategoryEntry = {id: 'grandChild1', parent: 'child1', short_name: '', name: '', community: null, provenance_iri: null, organisation: null, allow_projects: false, allow_software:false, properties: {}} + const grandChild2: CategoryEntry = {id: 'grandChild2', parent: 'child1', short_name: '', name: '', community: null, provenance_iri: null, organisation: null, allow_projects: false, allow_software:false, properties: {}} + const grandChild3: CategoryEntry = {id: 'grandChild3', parent: 'child2', short_name: '', name: '', community: null, provenance_iri: null, organisation: null, allow_projects: false, allow_software:false, properties: {}} + const grandChild4: CategoryEntry = {id: 'grandChild4', parent: 'child2', short_name: '', name: '', community: null, provenance_iri: null, organisation: null, allow_projects: false, allow_software:false, properties: {}} + const child1: CategoryEntry = {id: 'child1', parent: 'parent', short_name: '', name: '', community: null, provenance_iri: null, organisation: null, allow_projects: false, allow_software:false, properties: {}} + const child2: CategoryEntry = {id: 'child2', parent: 'parent', short_name: '', name: '', community: null, provenance_iri: null, organisation: null, allow_projects: false, allow_software:false, properties: {}} + const parent: CategoryEntry = {id: 'parent', parent: null, short_name: '', name: '', community: null, provenance_iri: null, organisation: null, allow_projects: false, allow_software:false, properties: {}} const entries = [ parent, diff --git a/frontend/components/layout/TagChipFilter.tsx b/frontend/components/layout/TagChipFilter.tsx index 76118d73a..151f58bed 100644 --- a/frontend/components/layout/TagChipFilter.tsx +++ b/frontend/components/layout/TagChipFilter.tsx @@ -9,12 +9,12 @@ import Chip from '@mui/material/Chip' import SearchIcon from '@mui/icons-material/Search' import Link from 'next/link' -type TagChipFilterProps={ +type TagChipFilterProps=Readonly<{ label: string, url?:string , title?: string capitalize?: boolean -} +}> export default function TagChipFilter({ url, label, diff --git a/frontend/components/projects/edit/organisations/EditProjectOrganisationsIndex.test.tsx b/frontend/components/projects/edit/organisations/EditProjectOrganisationsIndex.test.tsx index 09fd6ffae..3c4368467 100644 --- a/frontend/components/projects/edit/organisations/EditProjectOrganisationsIndex.test.tsx +++ b/frontend/components/projects/edit/organisations/EditProjectOrganisationsIndex.test.tsx @@ -357,7 +357,7 @@ describe('frontend/components/projects/edit/organisations/index.tsx', () => { fireEvent.click(categoriesBtn) // get organisation categories modal - const modal = screen.getByRole('dialog') + const modal = await screen.findByRole('dialog') // close modal const cancelBtn = within(modal).getByRole('button', { diff --git a/frontend/components/software/AboutLanguages.tsx b/frontend/components/software/AboutLanguages.tsx index 6ccc36811..b165f847d 100644 --- a/frontend/components/software/AboutLanguages.tsx +++ b/frontend/components/software/AboutLanguages.tsx @@ -1,8 +1,8 @@ // SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 - 2023 dv4all -// SPDX-FileCopyrightText: 2022 - 2024 Netherlands eScience Center +// SPDX-FileCopyrightText: 2022 - 2025 Netherlands eScience Center // SPDX-FileCopyrightText: 2022 Ewan Cahen (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) // SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) // SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all) (dv4all) // SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences @@ -92,8 +92,8 @@ export default function AboutLanguages({languages, platform}: } return ( - <> -
+
+
{label}
@@ -103,6 +103,6 @@ export default function AboutLanguages({languages, platform}: return })} - +
) } diff --git a/frontend/components/software/AboutLicense.tsx b/frontend/components/software/AboutLicense.tsx index 76bbb3e7a..1f74c43fc 100644 --- a/frontend/components/software/AboutLicense.tsx +++ b/frontend/components/software/AboutLicense.tsx @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all -// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2024 Netherlands eScience Center +// SPDX-FileCopyrightText: 2024 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 - 2025 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -12,8 +12,8 @@ import {LicenseForSoftware} from '~/types/SoftwareTypes' export default function AboutLicense({licenses}:{licenses:LicenseForSoftware[]}) { // console.log('AboutLicense...', licenses) return ( - <> -
+
+
License
@@ -42,6 +42,6 @@ export default function AboutLicense({licenses}:{licenses:LicenseForSoftware[]}) })} } - +
) } diff --git a/frontend/components/software/AboutPackageManagers.tsx b/frontend/components/software/AboutPackageManagers.tsx index d3ab20d18..7e890c419 100644 --- a/frontend/components/software/AboutPackageManagers.tsx +++ b/frontend/components/software/AboutPackageManagers.tsx @@ -45,8 +45,8 @@ function PackageManagerItem({item}:{item:PackageManager}){ export default function AboutPackageManagers({packages}:AboutPackageManagersProps) { if (packages?.length > 0){ return ( - <> -
+
+
@@ -55,7 +55,7 @@ export default function AboutPackageManagers({packages}:AboutPackageManagersProp
{packages.map(item=>)}
- +
) } // do not show section if no package managers diff --git a/frontend/components/software/AboutSection.tsx b/frontend/components/software/AboutSection.tsx index ed29e9682..5c12f26bc 100644 --- a/frontend/components/software/AboutSection.tsx +++ b/frontend/components/software/AboutSection.tsx @@ -2,8 +2,8 @@ // SPDX-FileCopyrightText: 2021 - 2023 dv4all // SPDX-FileCopyrightText: 2022 - 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences // SPDX-FileCopyrightText: 2022 Christian Meeßen (GFZ) -// SPDX-FileCopyrightText: 2023 - 2024 Dusan Mijatovic (Netherlands eScience Center) -// SPDX-FileCopyrightText: 2023 - 2024 Netherlands eScience Center +// SPDX-FileCopyrightText: 2023 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2023 - 2025 Netherlands eScience Center // SPDX-FileCopyrightText: 2023 Felix Mühlbauer (GFZ) // // SPDX-License-Identifier: Apache-2.0 @@ -12,9 +12,10 @@ import { ProgramingLanguages, CodePlatform, KeywordForSoftware, CategoriesForSoftware, - LicenseForSoftware} from '~/types/SoftwareTypes' -import {CategoriesWithHeadlines} from '~/components/category/CategoriesWithHeadlines' + LicenseForSoftware +} from '~/types/SoftwareTypes' import PageContainer from '~/components/layout/PageContainer' +import CategoriesSidebar from '~/components/software/CategoriesSidebar' import {PackageManager} from './edit/package-managers/apiPackageManager' import AboutStatement from './AboutStatement' import SoftwareKeywords from './SoftwareKeywords' @@ -44,40 +45,40 @@ export default function AboutSection(props:AboutSectionType) { repository, languages, platform, description_type = 'markdown', image_id, packages } = props - if (brand_name==='') return null - - // extract only license text - // const license = licenses?.map(item => item.license) - function getSoftwareLogo() { - if (image_id !== null) { - return ( - - ) - } - return null - } + if (brand_name==='') return null return ( - -
+ +
-
- {getSoftwareLogo()} - + + {/* SIDEBAR */} +
+ { + image_id ? + + : null + } + + + + + +
) diff --git a/frontend/components/software/AboutSourceCode.tsx b/frontend/components/software/AboutSourceCode.tsx index 04c5885c4..b262b2af2 100644 --- a/frontend/components/software/AboutSourceCode.tsx +++ b/frontend/components/software/AboutSourceCode.tsx @@ -58,14 +58,14 @@ export default function AboutSourceCode({repository,platform}: { repository: str } return ( - <> -
+
+
{code} Source code
{getIcon()}
- +
) } diff --git a/frontend/components/software/CategoriesSidebar.tsx b/frontend/components/software/CategoriesSidebar.tsx new file mode 100644 index 000000000..ca29456a6 --- /dev/null +++ b/frontend/components/software/CategoriesSidebar.tsx @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 - 2025 Netherlands eScience Center +// +// SPDX-License-Identifier: Apache-2.0 + +import {CategoryPath} from '~/types/Category' +import SidebarSection from '~/components/layout/SidebarSection' +import SidebarTitle from '~/components/layout/SidebarTitle' +import {CategoryChipFilter} from '~/components/category/CategoryChipFilter' +import {useCategoryTree} from '~/components/category/useCategoryTree' +import CategoryIcon from '~/components/category/CategoryIcon' + +export default function CategoriesSidebar({categories}:{categories:CategoryPath[]}) { + const tree = useCategoryTree(categories) + + // console.group('CategoriesSidebar') + // console.log('categories...', categories) + // console.log('tree...', tree) + // console.groupEnd() + + // each root category is separate sidebar section + return tree.map(node => { + const category = node.getValue() + const children = node.children() + + return ( + + + + {category.short_name} + +
+ +
+
+ ) + }) +} diff --git a/frontend/components/software/SoftwareKeywords.tsx b/frontend/components/software/SoftwareKeywords.tsx index 8c5df27fe..683f9243f 100644 --- a/frontend/components/software/SoftwareKeywords.tsx +++ b/frontend/components/software/SoftwareKeywords.tsx @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: 2022 Dusan Mijatovic (dv4all) // SPDX-FileCopyrightText: 2022 dv4all +// SPDX-FileCopyrightText: 2024 - 2025 Dusan Mijatovic (Netherlands eScience Center) +// SPDX-FileCopyrightText: 2024 - 2025 Netherlands eScience Center // SPDX-FileCopyrightText: 2024 Christian Meeßen (GFZ) -// SPDX-FileCopyrightText: 2024 Dusan Mijatovic (Netherlands eScience Center) // SPDX-FileCopyrightText: 2024 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences -// SPDX-FileCopyrightText: 2024 Netherlands eScience Center // // SPDX-License-Identifier: Apache-2.0 @@ -30,12 +30,12 @@ export default function SoftwareKeywords({keywords = []}: { keywords: KeywordFor } return ( - <> -
+
+
Keywords
{renderTags()} - +
) } diff --git a/frontend/components/software/edit/organisations/EditSoftwareOrganisationsIndex.test.tsx b/frontend/components/software/edit/organisations/EditSoftwareOrganisationsIndex.test.tsx index c3bc72042..158bf2fc1 100644 --- a/frontend/components/software/edit/organisations/EditSoftwareOrganisationsIndex.test.tsx +++ b/frontend/components/software/edit/organisations/EditSoftwareOrganisationsIndex.test.tsx @@ -392,7 +392,7 @@ describe('frontend/components/software/edit/organisations/index.tsx', () => { fireEvent.click(categoriesBtn) // get organisation categories modal - const modal = screen.getByRole('dialog') + const modal = await screen.findByRole('dialog') // close modal const cancelBtn = within(modal).getByRole('button', { diff --git a/frontend/components/software/edit/organisations/SortableOrganisationItem.tsx b/frontend/components/software/edit/organisations/SortableOrganisationItem.tsx index 16f5e59ea..be5697587 100644 --- a/frontend/components/software/edit/organisations/SortableOrganisationItem.tsx +++ b/frontend/components/software/edit/organisations/SortableOrganisationItem.tsx @@ -79,10 +79,10 @@ export default function SortableOrganisationsItem({organisation, pos, onEdit, on return ( -// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences -// -// SPDX-License-Identifier: Apache-2.0 - -import QuestionMarkIcon from '@mui/icons-material/QuestionMark' -import CategoryIcon from '@mui/icons-material/Category' -import ScienceIcon from '@mui/icons-material/Science' - -import type SvgIcon from '@mui/material/SvgIcon' - -// => This is a workaround before we can load icons dynamically (see #975). - -// create a component map with lowercase icon names as index -const iconMap = (() => { - const icons = { - QuestionMarkIcon, - CategoryIcon, - ScienceIcon, - // => extend the list above if necessary - } as Record - // magically generate icon names - return Object.keys(icons).reduce((map, key)=>{ - const iconName = key.toLowerCase().slice(0, -4) - map[iconName] = icons[key] - return map - },{} as Record) -})() - -type IconProps = { - name: string -} & React.ComponentProps - -export const Icon = ({name, ...props} : IconProps) => { - const MuiIcon = (name && iconMap[name.toLowerCase()]) || QuestionMarkIcon - return -} - - diff --git a/frontend/components/typography/SidebarHeadline.tsx b/frontend/components/typography/SidebarHeadline.tsx deleted file mode 100644 index 335cad340..000000000 --- a/frontend/components/typography/SidebarHeadline.tsx +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Felix Mühlbauer (GFZ) -// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences -// -// SPDX-License-Identifier: Apache-2.0 - -import {Icon} from './Icon' - -type SidebarHeadlineProps = { - iconName?: string - title: string -} -export const SidebarHeadline = ({iconName, title}: SidebarHeadlineProps) => { - return
- {iconName && } - {title} -
-} diff --git a/frontend/types/Category.ts b/frontend/types/Category.ts index 7d9401b66..e630dc41d 100644 --- a/frontend/types/Category.ts +++ b/frontend/types/Category.ts @@ -6,8 +6,10 @@ // // SPDX-License-Identifier: Apache-2.0 +import {MuiIconName} from '~/components/category/CategoryIcon' + export type CategoryProperties = { - icon?: string + icon?: MuiIconName is_highlight?: boolean subtitle?: string description?: string