-
Notifications
You must be signed in to change notification settings - Fork 76
subcommunities: improve search page #1242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
9967c36
7aca22e
eaf7d63
6aef16e
3061252
e118fba
4fa7155
d535752
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { i18next } from "@translations/invenio_rdm_records/i18next"; | ||
|
||
import React, { Component } from "react"; | ||
|
||
import { Menu } from "semantic-ui-react"; | ||
import PropTypes from "prop-types"; | ||
|
||
export class CommunitiesStatusFilter extends Component { | ||
render() { | ||
const { | ||
myCommunitiesOnClick, | ||
allCommunitiesOnClick, | ||
appID, | ||
allCommunitiesSelected, | ||
} = this.props; | ||
|
||
return ( | ||
<Menu role="tablist" className="theme-primary-menu" compact> | ||
<Menu.Item | ||
as="button" | ||
role="tab" | ||
id="all-communities-tab" | ||
aria-selected={allCommunitiesSelected} | ||
aria-controls={appID} | ||
name="All" | ||
active={allCommunitiesSelected} | ||
onClick={allCommunitiesOnClick} | ||
> | ||
{i18next.t("All")} | ||
</Menu.Item> | ||
<Menu.Item | ||
as="button" | ||
role="tab" | ||
id="my-communities-tab" | ||
aria-selected={!allCommunitiesSelected} | ||
aria-controls={appID} | ||
name="My communities" | ||
active={!allCommunitiesSelected} | ||
onClick={myCommunitiesOnClick} | ||
> | ||
{i18next.t("My communities")} | ||
</Menu.Item> | ||
</Menu> | ||
); | ||
} | ||
} | ||
|
||
CommunitiesStatusFilter.propTypes = { | ||
allCommunitiesOnClick: PropTypes.func.isRequired, | ||
myCommunitiesOnClick: PropTypes.func.isRequired, | ||
allCommunitiesSelected: PropTypes.bool, | ||
appID: PropTypes.string.isRequired, | ||
}; | ||
|
||
CommunitiesStatusFilter.defaultProps = { | ||
allCommunitiesSelected: true, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
// This file is part of Invenio-RDM | ||
// Copyright (C) 2024 CERN. | ||
// | ||
// Invenio-communities is free software; you can redistribute it and/or modify it | ||
// under the terms of the MIT License; see LICENSE file for more details. | ||
|
||
import { i18next } from "@translations/invenio_rdm_records/i18next"; | ||
import React, { Component } from "react"; | ||
import { OverridableContext } from "react-overridable"; | ||
import { InvenioSearchApi, ReactSearchKit, SearchBar, buildUID } from "react-searchkit"; | ||
import { Button, Grid } from "semantic-ui-react"; | ||
import PropTypes from "prop-types"; | ||
import { | ||
SearchConfigurationContext, | ||
SearchAppFacets, | ||
SearchAppResultsPane, | ||
} from "@js/invenio_search_ui/components"; | ||
import { GridResponsiveSidebarColumn } from "react-invenio-forms"; | ||
import { CommunitiesStatusFilter } from "./CommunitiesStatusFilter"; | ||
|
||
export class CommunitySelectionSearch extends Component { | ||
constructor(props) { | ||
super(props); | ||
const { | ||
apiConfigs: { allCommunities }, | ||
} = this.props; | ||
|
||
this.state = { | ||
selectedConfig: allCommunities, | ||
sidebarVisible: false, | ||
}; | ||
} | ||
|
||
/** | ||
* Namespaces each component object with the specified appID. | ||
* This is needed since the `CommunitySelectionSearch` component | ||
* uses two search applications with distinct configs (e.g. to search in "All" or "My" communities) | ||
*/ | ||
prefixAppID(components, appID) { | ||
// iterate components and prefix them with ".appID" | ||
return Object.fromEntries( | ||
Object.entries(components).map(([key, value]) => [`${appID}.${key}`, value]) | ||
); | ||
} | ||
|
||
render() { | ||
const { | ||
selectedConfig: { | ||
searchApi: selectedSearchApi, | ||
appId: selectedAppId, | ||
initialQueryState: selectedInitialQueryState, | ||
toggleText, | ||
}, | ||
sidebarVisible, | ||
} = this.state; | ||
|
||
const { | ||
apiConfigs: { allCommunities, myCommunities }, | ||
autofocus, | ||
communitiesStatusFilterEnabled, | ||
config, | ||
overriddenComponents, | ||
} = this.props; | ||
|
||
const searchApi = new InvenioSearchApi(selectedSearchApi); | ||
|
||
const validatedComponents = this.prefixAppID(overriddenComponents, selectedAppId); | ||
|
||
const layoutOptions = { | ||
listView: true, | ||
gridView: false, | ||
}; | ||
|
||
const context = { | ||
selectedAppId, | ||
buildUID: (element) => buildUID(element, "", selectedAppId), | ||
...config, | ||
}; | ||
|
||
return ( | ||
<OverridableContext.Provider value={validatedComponents}> | ||
<SearchConfigurationContext.Provider value={context}> | ||
<ReactSearchKit | ||
appName={selectedAppId} | ||
urlHandlerApi={{ enabled: false }} | ||
searchApi={searchApi} | ||
key={selectedAppId} | ||
initialQueryState={selectedInitialQueryState} | ||
defaultSortingOnEmptyQueryString={{ sortBy: "bestmatch" }} | ||
> | ||
<Grid padded> | ||
<Grid.Row> | ||
{/* Start burger menu for mobile and tablet only */} | ||
<Grid.Column only="mobile tablet" mobile={4} tablet={1}> | ||
<Button | ||
basic | ||
size="medium" | ||
icon="sliders" | ||
onClick={() => this.setState({ sidebarVisible: true })} | ||
aria-label={i18next.t("Filter results")} | ||
className="rel-mb-1" | ||
/> | ||
{/* End burger menu */} | ||
</Grid.Column> | ||
{config.aggs && <Grid.Column computer={4} />} | ||
{communitiesStatusFilterEnabled && ( | ||
<Grid.Column | ||
mobile={8} | ||
tablet={4} | ||
computer={4} | ||
floated="right" | ||
className="text-align-right-mobile" | ||
> | ||
<CommunitiesStatusFilter | ||
myCommunitiesOnClick={() => { | ||
this.setState({ | ||
selectedConfig: myCommunities, | ||
}); | ||
}} | ||
allCommunitiesOnClick={() => { | ||
this.setState({ | ||
selectedConfig: allCommunities, | ||
}); | ||
}} | ||
appID={selectedAppId} | ||
allCommunitiesSelected={selectedAppId === allCommunities.appId} | ||
/> | ||
</Grid.Column> | ||
)} | ||
<Grid.Column | ||
mobile={16} | ||
tablet={11} | ||
computer={communitiesStatusFilterEnabled ? 8 : 12} | ||
verticalAlign="middle" | ||
floated="right" | ||
> | ||
<SearchBar | ||
placeholder={toggleText} | ||
autofocus={autofocus} | ||
actionProps={{ | ||
"icon": "search", | ||
"content": null, | ||
"className": "search", | ||
"aria-label": i18next.t("Search"), | ||
}} | ||
className="middle aligned" | ||
/> | ||
</Grid.Column> | ||
</Grid.Row> | ||
|
||
<Grid.Row className="community-list-results pt-2"> | ||
<GridResponsiveSidebarColumn | ||
width={4} | ||
open={sidebarVisible} | ||
onHideClick={() => this.setState({ sidebarVisible: false })} | ||
> | ||
<SearchAppFacets aggs={config.aggs} appName={selectedAppId} /> | ||
</GridResponsiveSidebarColumn> | ||
<Grid.Column mobile={16} tablet={16} computer={12}> | ||
<SearchAppResultsPane | ||
appName={selectedAppId} | ||
layoutOptions={layoutOptions} | ||
/> | ||
</Grid.Column> | ||
</Grid.Row> | ||
</Grid> | ||
</ReactSearchKit> | ||
</SearchConfigurationContext.Provider> | ||
</OverridableContext.Provider> | ||
); | ||
} | ||
} | ||
|
||
CommunitySelectionSearch.propTypes = { | ||
apiConfigs: PropTypes.shape({ | ||
allCommunities: PropTypes.shape({ | ||
appId: PropTypes.string.isRequired, | ||
initialQueryState: PropTypes.object.isRequired, | ||
searchApi: PropTypes.object.isRequired, | ||
}), | ||
myCommunities: PropTypes.shape({ | ||
appId: PropTypes.string.isRequired, | ||
initialQueryState: PropTypes.object.isRequired, | ||
searchApi: PropTypes.object.isRequired, | ||
}), | ||
}), | ||
autofocus: PropTypes.bool, | ||
overriddenComponents: PropTypes.object, | ||
config: PropTypes.object.isRequired, | ||
communitiesStatusFilterEnabled: PropTypes.bool, | ||
}; | ||
|
||
CommunitySelectionSearch.defaultProps = { | ||
autofocus: true, | ||
apiConfigs: { | ||
allCommunities: { | ||
initialQueryState: { size: 5, page: 1, sortBy: "bestmatch" }, | ||
searchApi: { | ||
axios: { | ||
url: "/api/communities", | ||
headers: { Accept: "application/vnd.inveniordm.v1+json" }, | ||
}, | ||
}, | ||
appId: "ReactInvenioDeposit.CommunitySelectionSearch.AllCommunities", | ||
toggleText: "Search in all communities", | ||
}, | ||
myCommunities: { | ||
initialQueryState: { size: 5, page: 1, sortBy: "bestmatch" }, | ||
searchApi: { | ||
axios: { | ||
url: "/api/user/communities", | ||
headers: { Accept: "application/vnd.inveniordm.v1+json" }, | ||
}, | ||
}, | ||
appId: "ReactInvenioDeposit.CommunitySelectionSearch.MyCommunities", | ||
toggleText: "Search in my communities", | ||
}, | ||
}, | ||
overriddenComponents: {}, | ||
communitiesStatusFilterEnabled: true, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// This file is part of Invenio | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file is almost the same as https://github.com/inveniosoftware/invenio-requests/blob/master/invenio_requests/assets/semantic-ui/js/invenio_requests/search/RequestsResults.js (just renamed it). That makes me think this can be moved to a higher module so both requests and communities can reuse it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe also in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a search aware component, we should move it to invenio-search-ui? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Exactly, I was just messaging Pablo about this. +1 on moving it to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea was to create a base package, similar to invenio-base for Python code, that serves as a catalog of components. This package should remain stateless, without any specific features or logic, making it a simple dependency that can be included in other modules. While I understand the rationale behind associating a component that displays search results with invenio-search-ui, I believe that it doesn't align with the intended purpose of the base package. The base package is meant to be a neutral catalog, and incorporating such a component into invenio-search-ui would go against these criteria. I am sure that we will find a case where we will need that component in a place where we don't depend on invenio-search-ui. Happy to hear/read counterarguments. |
||
// Copyright (C) 2024 CERN. | ||
// | ||
// Invenio is free software; you can redistribute it and/or modify it | ||
// under the terms of the MIT License; see LICENSE file for more details. | ||
|
||
import { InvenioSearchPagination } from "@js/invenio_search_ui/components"; | ||
import { i18next } from "@translations/invenio_requests/i18next"; | ||
import PropTypes from "prop-types"; | ||
import React from "react"; | ||
import { Count, ResultsList, Sort } from "react-searchkit"; | ||
import { Grid, Segment } from "semantic-ui-react"; | ||
|
||
export const SubcommunityResults = ({ | ||
alejandromumo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
sortOptions, | ||
paginationOptions, | ||
currentResultsState, | ||
}) => { | ||
const { total } = currentResultsState.data; | ||
return ( | ||
total && ( | ||
<Grid> | ||
<Grid.Row> | ||
<Grid.Column width={16}> | ||
<Segment> | ||
<Grid> | ||
<Grid.Row | ||
verticalAlign="middle" | ||
className="small pt-5 pb-5 highlight-background" | ||
> | ||
<Grid.Column width={4}> | ||
<Count | ||
label={() => ( | ||
<> | ||
{i18next.t("{{count}} results found", { | ||
count: total, | ||
})} | ||
</> | ||
)} | ||
/> | ||
</Grid.Column> | ||
<Grid.Column width={12} textAlign="right"> | ||
{sortOptions && ( | ||
<Sort | ||
values={sortOptions} | ||
label={(cmp) => ( | ||
<> | ||
<label className="mr-10">{i18next.t("Sort by")}</label> | ||
{cmp} | ||
</> | ||
)} | ||
/> | ||
)} | ||
</Grid.Column> | ||
</Grid.Row> | ||
<Grid.Row> | ||
<Grid.Column> | ||
<ResultsList /> | ||
</Grid.Column> | ||
</Grid.Row> | ||
</Grid> | ||
</Segment> | ||
</Grid.Column> | ||
</Grid.Row> | ||
<InvenioSearchPagination paginationOptions={paginationOptions} /> | ||
</Grid> | ||
) | ||
); | ||
}; | ||
|
||
SubcommunityResults.propTypes = { | ||
sortOptions: PropTypes.object.isRequired, | ||
paginationOptions: PropTypes.object.isRequired, | ||
currentResultsState: PropTypes.object.isRequired, | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.