Commit 221f3960 authored by Stefan Probst's avatar Stefan Probst
Browse files

fix: fix item categories

parent 15a053b4
Pipeline #150880 failed with stages
in 4 minutes and 35 seconds
......@@ -10,7 +10,7 @@
name="description"
content="Social Sciences & Humanities Open Marketplace"
/>
<link rel="apple-touch-icon" href="logo192.png" />
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>Social Sciences & Humanities Open Marketplace</title>
<script type="text/javascript">
......
......@@ -16,10 +16,15 @@ const createMarketplaceAPI = ({ baseUrl, prefix = '/api' }) => ({
method: 'get',
path: `${prefix}/datasets/${id}`,
}),
getPublicationById: ({ id }) => ({
baseUrl,
method: 'get',
path: `${prefix}/publications/${id}`,
}),
getToolById: ({ id }) => ({
baseUrl,
method: 'get',
path: `${prefix}/tools/${id}`,
path: `${prefix}/tools-services/${id}`,
}),
getTrainingMaterialById: ({ id }) => ({
baseUrl,
......
......@@ -8,6 +8,7 @@ import Home from '../../pages/Home/Home'
import Login from '../../pages/Login/Login'
import NotFound from '../../pages/NotFound/NotFound'
import PrivacyPolicy from '../../pages/PrivacyPolicy/PrivacyPolicy'
import Publication from '../../pages/Publication/Publication'
import Search from '../../pages/Search/Search'
import Tool from '../../pages/Tool/Tool'
import TrainingMaterial from '../../pages/TrainingMaterial/TrainingMaterial'
......@@ -27,13 +28,14 @@ const App = () => {
<Route component={About} path="/about" />
<Route component={Browse} path="/browse" />
<Route component={Contact} path="/contact" />
<Route component={Dataset} path="/datasets/:id" />
<Route component={Dataset} path="/dataset/:id" />
<Route component={Login} path="/login" />
<Route component={PrivacyPolicy} path="/privacy-policy" />
<Route component={Publication} path="/publication/:id" />
<Route component={Search} path="/search" />
<Route component={Tool} path="/tools/:id" />
<Route component={TrainingMaterial} path="/training-materials/:id" />
<Route component={Workflow} path="/workflows/:id" />
<Route component={Tool} path="/tool-or-service/:id" />
<Route component={TrainingMaterial} path="/training-material/:id" />
<Route component={Workflow} path="/workflow/:id" />
<Route component={NotFound} />
</Switch>
</ErrorBoundary>
......
......@@ -14,7 +14,6 @@ import Text from '../../elements/Text/Text'
import { fetchSearchResults } from '../../store/actions/items'
import { REQUEST_STATUS } from '../../store/constants'
import { selectors } from '../../store/reducers'
import { pluralize } from '../../utils'
const MAX_BROWSE_LINKS = 20
const MAX_LAST_ADDED = 5
......@@ -294,10 +293,7 @@ const Item = ({ item }) => {
{item.description}
</Text>
<Flex css={css({ justifyContent: 'flex-end' })}>
<Link
css={css({ fontSize: 14 })}
to={`/${pluralize(item.category)}/${item.id}`}
>
<Link css={css({ fontSize: 14 })} to={`/${item.category}/${item.id}`}>
Read more
</Link>
</Flex>
......
......@@ -2,7 +2,6 @@ import css from '@styled-system/css'
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import styled from 'styled-components/macro'
import { ITEM_CATEGORY } from '../../constants'
import Box from '../../elements/Box/Box'
import Button from '../../elements/Button/Button'
import Container from '../../elements/Container/Container'
......@@ -14,7 +13,8 @@ import { ReactComponent as SSHOCMPLogo } from '../../images/logo.svg'
import { userLogin, userLogout } from '../../store/actions/user'
import { REQUEST_STATUS } from '../../store/constants'
import { selectors } from '../../store/reducers'
import { pluralize } from '../../utils'
import { useEffect } from 'react'
import { fetchItemCategories } from '../../store/actions/itemCategories'
const Logo = () => (
<nav>
......@@ -52,60 +52,72 @@ const NavLink = styled(Link).attrs({ variant: 'nav' })(
})
)
const Header = () => (
<Box
as="header"
css={css({
borderBottomColor: 'subtle',
borderBottomStyle: 'solid',
borderBottomWidth: 1,
position: 'relative',
})}
>
<Container
as={Flex}
const Header = () => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchItemCategories())
}, [dispatch])
const itemCategories = useSelector(state =>
selectors.itemCategories.selectAllResources(state)
)
return (
<Box
as="header"
css={css({
alignItems: 'center',
justifyContent: 'space-between',
py: 0,
px: 1,
borderBottomColor: 'subtle',
borderBottomStyle: 'solid',
borderBottomWidth: 1,
position: 'relative',
})}
variant="wide"
>
<Logo />
<Stack
css={{
alignItems: 'flex-end',
alignSelf: 'stretch',
<Container
as={Flex}
css={css({
alignItems: 'center',
justifyContent: 'space-between',
}}
py: 0,
px: 1,
})}
variant="wide"
>
<Flex css={css({ alignItems: 'stretch', height: 40, py: 1 })}>
<LoginBar />
</Flex>
<nav>
<FlexList>
{Object.entries(ITEM_CATEGORY).map(([key, label]) => (
<NavItem key={key}>
<NavLink to={`/search?categories=${key}&sort=label`}>
{pluralize(label)}
</NavLink>
<Logo />
<Stack
css={{
alignItems: 'flex-end',
alignSelf: 'stretch',
justifyContent: 'space-between',
}}
>
<Flex css={css({ alignItems: 'stretch', height: 40, py: 1 })}>
<LoginBar />
</Flex>
<nav>
<FlexList>
{Object.entries(itemCategories || {}).map(([key, label]) => (
<NavItem key={key}>
<NavLink to={`/search?categories=${key}&sort=label`}>
{label}
</NavLink>
</NavItem>
))}
<Separator />
<NavItem>
<NavLink to="/browse">Browse</NavLink>
</NavItem>
))}
<Separator />
<NavItem>
<NavLink to="/browse">Browse</NavLink>
</NavItem>
<Separator />
<NavItem>
<NavLink to="/about">About</NavLink>
</NavItem>
</FlexList>
</nav>
</Stack>
</Container>
</Box>
)
<Separator />
<NavItem>
<NavLink to="/about">About</NavLink>
</NavItem>
</FlexList>
</nav>
</Stack>
</Container>
</Box>
)
}
const Separator = () => (
<NavItem css={css({ alignItems: 'center', display: 'flex', mx: 2 })}>
......
......@@ -162,7 +162,7 @@ const ImageCarousel = ({ images }) => {
)
}
const ItemDetails = ({ request, resource }) => {
const ItemDetails = ({ request, resource, itemCategories }) => {
if (resource) {
const thumbnail = resource.properties.find(
property => property.type.code === 'thumbnail'
......@@ -218,7 +218,10 @@ const ItemDetails = ({ request, resource }) => {
)}
/>
<ResourceSpecificDetails resource={resource} />
<RelatedItems items={resource.relatedItems} />
<RelatedItems
items={resource.relatedItems}
itemCategories={itemCategories}
/>
</>
)
}
......
......@@ -58,7 +58,11 @@ const ItemScreenContainer = ({ fetchItem, id }) => {
export const ItemScreen = ({ request, resource, itemCategories }) => (
<Flex css={css({ my: 6 })}>
<Box css={{ flex: 3 }}>
<ItemDetails request={request} resource={resource} />
<ItemDetails
request={request}
resource={resource}
itemCategories={itemCategories}
/>
</Box>
<aside
css={css({
......
import css from '@styled-system/css'
import React, { Fragment } from 'react'
import 'styled-components/macro'
import Dropdown from '../../elements/Dropdown/Dropdown'
import Button from '../../elements/Button/Button'
import Heading from '../../elements/Heading/Heading'
import Icon from '../../elements/Icon/Icon'
import Link from '../../elements/Link/Link'
......@@ -25,12 +25,17 @@ const ItemSidebar = ({ resource, itemCategories }) => {
const type = itemCategories?.[resource.category] || 'Resource'
const [url] = resource.accessibleAt || []
if (!url) return null
const links = [
{
label: (
<>
return (
<>
{url ? (
<Button
as="a"
href={url}
size="large"
variant="primary"
css={css({ mb: 6 })}
>
Go to {type}{' '}
<Icon
css={css({
......@@ -41,25 +46,9 @@ const ItemSidebar = ({ resource, itemCategories }) => {
icon="link"
width="0.6em"
/>
</>
),
path: url,
},
]
return (
<>
<Dropdown
aria-label="Versions"
links={links}
size="large"
// TODO: This whole ui element is a bit weird, since
// `accessibleAt` can be empty, in which case we want to
// disable the button, but we might still get older/newerVersions
// for which we need the dropdown
variant={resource.accessibleAt ? 'primary' : undefined}
/>
<Heading as="h2" css={css({ mt: 6 })} variant="h4">
</Button>
) : null}
<Heading as="h2" variant="h4">
Details
</Heading>
......
import css from '@styled-system/css'
import React from 'react'
import 'styled-components/macro'
import { ITEM_CATEGORY } from '../../constants'
import Badge from '../../elements/Badge/Badge'
import Box from '../../elements/Box/Box'
import Flex from '../../elements/Flex/Flex'
......@@ -14,7 +13,7 @@ import PlainText from '../PlainText/PainText'
const MAX_DESCRIPTION_LENGTH = 200
// TODO: pretty much duplicated from <SearchResult />
const RelatedItem = ({ item }) => (
const RelatedItem = ({ item, itemCategories }) => (
<Flex
css={css({
bg: 'subtler',
......@@ -34,23 +33,12 @@ const RelatedItem = ({ item }) => (
mb: 2,
})}
>
<Link
css={css({ flex: 1, mr: 2 })}
to={`/${item.category}s/${item.id}`}
>
<Link css={css({ flex: 1, mr: 2 })} to={`/${item.category}/${item.id}`}>
<Heading as="h3" variant="h4">
{item.label}
</Heading>
</Link>
{/* FIXME: We don't get additional metadata for related items */}
{/* <Badge>
{
item.properties.find(
property => property.type.code === 'object-type'
).concept.label
}
</Badge> */}
<Badge>{ITEM_CATEGORY[item.category]}</Badge>
<Badge>{itemCategories?.[item.category]}</Badge>
</Flex>
<Box css={css({ my: 2 })}>
......@@ -60,14 +48,6 @@ const RelatedItem = ({ item }) => (
.map(contributor => contributor.actor.name)
.join(', ')}
</Text>
{/* FIXME: We don't get additional metadata for related items */}
{/* <Text css={css({ color: 'grey.900' })} size="small">
<span css={css({ color: 'grey.800' })}>Activities: </span>
{item.properties
.filter(property => property.type.code === 'activity')
.map(property => property.concept?.label ?? property.value)
.join(', ')}
</Text> */}
</Box>
<Text css={css({ fontSize: 1, lineHeight: 'large', my: 3 })}>
......
import css from '@styled-system/css'
import React, { Fragment } from 'react'
import 'styled-components/macro'
import { ITEM_CATEGORY, ITEM_FACETS } from '../../constants'
import { ITEM_FACETS } from '../../constants'
import Box from '../../elements/Box/Box'
import Checkbox from '../../elements/Checkbox/Checkbox'
import Flex from '../../elements/Flex/Flex'
import Heading from '../../elements/Heading/Heading'
import Link from '../../elements/Link/Link'
import Separator from '../../elements/Separator/Separator'
import { pluralize } from '../../utils'
const CategoryCheckbox = ({
category,
......@@ -29,7 +28,7 @@ const CategoryCheckbox = ({
onChange={onChange}
value={category}
>
{pluralize(label)} {count ? `(${count})` : null}
{label} {count ? `(${count})` : null}
</Checkbox>
</Flex>
)
......@@ -48,17 +47,27 @@ const SearchFacets = ({
onSearchParamsChange,
request,
searchParams,
itemCategories,
}) => {
const { categories, facets, query, sort } = searchParams
const { info } = request || {}
function getCategoryResults(category) {
return (
info?.categories?.[category]?.count ??
collection.info?.categories?.[category]?.count
)
}
const possibleFacets = info?.facets || collection?.info?.facets || {}
const possibleCategories = categories.filter(getCategoryResults)
const handleChangeCategories = event => {
const { checked, value } = event.target
const updatedCategories = checked
? categories.concat(value)
: categories.filter(category => category !== value)
? possibleCategories.concat(value)
: possibleCategories.filter(category => category !== value)
// Always reset to first page, so we don't end up on a page larger
// than Math.ceil((results.length / pageSize)) when deselecting a category
......@@ -91,7 +100,7 @@ const SearchFacets = ({
}
onSearchParamsChange({
categories,
categories: possibleCategories,
facets: nextFacets,
// page,
query,
......@@ -127,17 +136,21 @@ const SearchFacets = ({
Categories
</Heading>
{Object.entries(ITEM_CATEGORY).map(([category, label]) => (
<CategoryCheckbox
category={category}
categories={categories}
collection={collection}
info={info}
key={category}
label={label}
onChange={handleChangeCategories}
/>
))}
{Object.entries(itemCategories || {})
.filter(([category]) => {
return Boolean(getCategoryResults(category))
})
.map(([category, label]) => (
<CategoryCheckbox
category={category}
categories={categories}
collection={collection}
info={info}
key={category}
label={label}
onChange={handleChangeCategories}
/>
))}
{Object.entries(possibleFacets).map(([key, value], i, arr) => {
const checkedFacets = facets[key] || []
......
import css from '@styled-system/css'
import React, { useState } from 'react'
import React, { useState, useEffect, useMemo } from 'react'
import 'styled-components/macro'
import Button from '../../elements/Button/Button'
import Flex from '../../elements/Flex/Flex'
import Input from '../../elements/Input/Input'
import Select from '../../elements/Select/Select'
import { useSearchParams } from '../../utils'
import { ITEM_CATEGORY } from '../../constants'
const categories = [
{ label: 'All Categories', value: '' },
...Object.entries(ITEM_CATEGORY).map(([value, label]) => ({
label: `${label}s`,
value,
})),
]
import { fetchItemCategories } from '../../store/actions/itemCategories'
import { useDispatch, useSelector } from 'react-redux'
import { selectors } from '../../store/reducers'
const SearchForm = props => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchItemCategories())
}, [dispatch])
const itemCategories = useSelector(state =>
selectors.itemCategories.selectAllResources(state)
)
const categories = useMemo(
() => [
{ label: 'All Categories', value: '' },
...Object.entries(itemCategories || {}).map(([value, label]) => ({
label,
value,
})),
],
[itemCategories]
)
const [, setSearchParams] = useSearchParams()
const [category, setCategory] = useState(categories[0])
const [query, setQuery] = useState('')
......
......@@ -9,7 +9,6 @@ import Icon from '../../elements/Icon/Icon'
import Link from '../../elements/Link/Link'
import Placeholder from '../../elements/Placeholder/Placeholder'
import Text from '../../elements/Text/Text'
import { pluralize } from '../../utils'
import PlainText from '../PlainText/PainText'
const MAX_DESCRIPTION_LENGTH = 400
......@@ -59,7 +58,7 @@ const SearchResult = ({ result, itemCategories }) => (
})}
>
<Box css={css({ flex: 1, mr: 2 })}>
<Link to={`/${pluralize(result.category)}/${result.id}`}>
<Link to={`/${result.category}/${result.id}`}>
<Heading as="h3" css={{ display: 'inline-block' }} variant="h4">
{result.label}
</Heading>
......@@ -88,9 +87,7 @@ const SearchResult = ({ result, itemCategories }) => (
</PlainText>
</Text>
<Flex css={css({ justifyContent: 'flex-end' })}>
<Link to={`/${pluralize(result.category)}/${result.id}`}>
Read more
</Link>
<Link to={`/${result.category}/${result.id}`}>Read more</Link>
</Flex>
</Box>
</SearchResultContainer>
......
......@@ -132,8 +132,6 @@ const SearchResults = ({
// TODO: is there *any* support for multiple search categories in matomo?
const selectedCategories = `categories: ${categories.join(', ') ||
'<empty>'}`
const selectedTypes = `types: ${facets['object-type']?.join(', ') ||
'<empty>'}`
const selectedActivities = `activities: ${facets['activity']?.join(
', '
) || '<empty>'}`
......@@ -144,7 +142,6 @@ const SearchResults = ({
const filters = [
selectedCategories,
selectedActivities,
selectedTypes,
selectedSources,
selectedKeywords,
].join(' / ')
......
......@@ -110,6 +110,7 @@ export const SearchScreen = ({
onSearchParamsChange={onSearchParamsChange}
request={request}
searchParams={searchParams}
itemCategories={itemCategories}
/>
</Sidebar>
<SearchResults
......
export const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || ''
// these are only used for storybook
export const ITEM_CATEGORY = {
tool: 'Tool',
'training-material': 'Training Material',
'tool-or-service': 'Tools & Services',
'training-material': 'Training Materials',
workflow: 'Workflow',
dataset: 'Dataset',
step: 'Steps',
publication: 'Publications',
}
export const ITEM_FACETS = {
'object-type': 'Types',
activity: 'Activities',
keyword: 'Keywords',
source: 'Sources',
......
......@@ -294,7 +294,7 @@ const getPath = icon => {
),
}
case 'tool':