diff --git a/src/divtree/ui/c/TreeExplorer.tsx b/src/divtree/ui/c/TreeExplorer.tsx index 4722e9ed672c48e0431953f4d603b6cc9e7c0aa2..658e2369d91e2cec7093bf57a62834a8214de2e7 100644 --- a/src/divtree/ui/c/TreeExplorer.tsx +++ b/src/divtree/ui/c/TreeExplorer.tsx @@ -2,13 +2,21 @@ import * as React from 'react'; import { withStyles, WithStyles, createStyles } from '@material-ui/core/styles'; import { WithTranslation, withTranslation } from 'react-i18next'; +// Model +import Accession from 'model/accession/Accession'; +import FilteredPage from 'model/FilteredPage'; +import Page from 'model/Page'; + import ConfiguredTree from 'crop/ui/c/ConfiguredTree'; -// import Button from '@material-ui/core/Button/Button'; +import Button from '@material-ui/core/Button'; import * as classnames from 'classnames'; import { throttle } from 'lodash'; import Card, { CardContent, CardHeader } from 'ui/common/Card'; -import IconButton from '@material-ui/core/IconButton/IconButton'; +import IconButton from '@material-ui/core/IconButton'; import CloseIcon from '@material-ui/icons/Close'; +import PagedLoader from 'ui/common/PagedLoader'; +import Loading from 'ui/common/Loading'; +import AccessionCard from 'accessions/ui/c/AccessionCard'; const styles = (theme) => createStyles({ @@ -30,9 +38,11 @@ const styles = (theme) => createStyles({ position: 'absolute', backgroundColor: 'white', border: 'solid 2px grey', - visibility: 'hidden', - display: 'flex', + // visibility: 'hidden', + display: 'none', + // display: 'flex', flexDirection: 'column', + padding: '5px', }, popupData: { fontSize: '1rem', @@ -41,14 +51,13 @@ const styles = (theme) => createStyles({ textAlign: 'center', }, visible: { - visibility: 'visible', + display: 'flex', + // visibility: 'visible', }, node: { position: 'absolute', - // width: 'calc(90% - 20px)', width: 'max-content', textAlign: 'right', - // left: 'calc(-90% + 40px)', right: '-10px', }, leafNode: { @@ -58,14 +67,14 @@ const styles = (theme) => createStyles({ display: 'inline-block', verticalAlign: 'top', whiteSpace: 'nowrap', + lineHeight: '1.38', }, accessionList: { width: '400px', position: 'absolute', right: 0, - [theme.breakpoints.down('md')]: { - width: '300px', - }, + height: '100%', + overflow: 'scroll', [theme.breakpoints.down('xs')]: { width: '100vw', }, @@ -75,6 +84,11 @@ const styles = (theme) => createStyles({ justifyContent: 'space-between', alignItems: 'center', }, + stickyHeader: { + position: 'sticky', + top: 0, + backgroundColor: 'white', + }, }); class NodeLabel extends React.PureComponent { @@ -84,17 +98,19 @@ class NodeLabel extends React.PureComponent { }; private onClick = (e) => { - const { nodeData, clickHandler } = this.props; + const { nodeData, clickHandler /* , expandCallback */ } = this.props; if (this.state.count === 1) { console.log('expand!'); + // pass click event to the node for expanding clearTimeout(this.timeout); + // expandCallback(); this.setState({ count: 0 }); return; } const x = e.clientX; const y = e.clientY; - e.stopPropagation(); + e.stopPropagation(); // prevent expanding this.setState({ count: 1 }); this.timeout = setTimeout(() => { @@ -117,12 +133,15 @@ class NodeLabel extends React.PureComponent { interface ITreeExplorerProps extends React.ClassAttributes, WithTranslation, WithStyles { treeData: any; + loadNodeAccessions: (nodeKey: string) => void; + loadMoreNodeAccessions: (paged: Page) => void; + accessions: FilteredPage; } class TreeExplorer extends React.Component { private readonly treeWrapperRef: React.RefObject; private readonly popupRef: React.RefObject; - private LABEL_HEIGHT = 16; + private LABEL_HEIGHT = 20; public constructor(props: ITreeExplorerProps, context: any) { super(props, context); @@ -141,12 +160,14 @@ class TreeExplorer extends React.Component { public componentDidMount(): void { if (typeof window !== 'undefined') { window.addEventListener('wheel', this.throttledHide); + document.documentElement.classList.add('modal-open'); } } public componentWillUnmount(): void { if (typeof window !== 'undefined') { window.removeEventListener('wheel', this.throttledHide); + document.documentElement.classList.remove('modal-open'); } } @@ -172,36 +193,54 @@ class TreeExplorer extends React.Component { private throttledHide = throttle(this.hidePopup, 1000); private showList = (e) => { + const { loadNodeAccessions } = this.props; console.log('show list click!'); - // e.stopPropagation(); + e.stopPropagation(); this.setState({ accessionListIsOpen: true }); + loadNodeAccessions(this.state.popupData.nodeKey); }; private closeAccessionList = () => { this.setState({ accessionListIsOpen: false }) }; + private renderAccession = (s: Accession, index: number) => { + return
; + }; + + private handleWrapperClick = (e) => { + console.log('wrapper click!!', e.target); + if (e.target.tagName !== 'H6' && this.state.popupData) { + setTimeout(this.hidePopup, 1); // prevents rerender on tree's drag start} + } + }; + + // private onNodeExpand = () => { + // setTimeout(this.hidePopup, 1); + // // this.hidePopup(); + // }; + + private onButtonMouseDown = (e) => e.stopPropagation(); + public render() { - const { treeData, classes } = this.props; + const { treeData, classes, accessions, loadMoreNodeAccessions, t } = this.props; const { x, y, popupData, accessionListIsOpen } = this.state; - // todo: translations return ( -
+
, + nodeLabelComponent={ { + render: , foreignObjectWrapper: { - y: -(this.LABEL_HEIGHT / 2), + y: -this.LABEL_HEIGHT / 2, x: -10, height: this.LABEL_HEIGHT, width: 10, }, - }} - // onClickHandler={ this.onNodeClick } + } } />
{ { popupData && <>
{ popupData.name }
-
Node key: { popupData.nodeKey }
+
+ { t('divtree.public.c.treeExplorer.nodeKey', { what: popupData.nodeKey }) } +
}
- { /* fixme: Button causes fontSize issues in header */ } -
Accessions List
+
{ accessionListIsOpen &&
@@ -236,7 +279,17 @@ class TreeExplorer extends React.Component { } /> - TEST + { accessions && accessions.content && accessions.content.length > 0 && + + } + { accessions && accessions.content && accessions.content.length === 0 && +
{ t('divtree.public.c.treeExplorer.noAccessions') }
+ } + { !accessions && }
} diff --git a/workspaces/client/src/model/accession/AccessionFilter.ts b/workspaces/client/src/model/accession/AccessionFilter.ts index af4ac8cd8a3d44fada71552987c35908772dc45e..ce68d322ba4840463ba7c8e42504d4740df4e350 100644 --- a/workspaces/client/src/model/accession/AccessionFilter.ts +++ b/workspaces/client/src/model/accession/AccessionFilter.ts @@ -46,6 +46,7 @@ class AccessionFilter { public subsets?: string[]; public datasets?: string[]; public networks?: string[]; + public nodeKey?: string; public NOT?: AccessionFilter; public NULL?: string[]; diff --git a/workspaces/ui-express/locales/en/translations.json b/workspaces/ui-express/locales/en/translations.json index cc2433d20cfc7a854921c40f1ffad0ecd55bb5fe..c5686876edb9b4ba7ea759d1ba0f97d37e0a9984 100644 --- a/workspaces/ui-express/locales/en/translations.json +++ b/workspaces/ui-express/locales/en/translations.json @@ -1788,7 +1788,14 @@ "map": "Accession map", "mapDescription": "Explore diversity tree accessions on the map", "browse": "Filter accessions", - "browseDescription": "Apply custom filters to accessions in this diversity tree" + "browseDescription": "Apply custom filters to accessions in this diversity tree", + "view": "View", + "info": "Info" + }, + "treeExplorer": { + "nodeKey": "Node key: {{what}}", + "accessionList": "Accession list", + "noAccessions": "No accessions" } }, "f": { diff --git a/workspaces/ui-express/src/crop/ui/c/ConfiguredTree.tsx b/workspaces/ui-express/src/crop/ui/c/ConfiguredTree.tsx index 6c1ae6a398880923cfdc980c36fc04225a9a435d..e37da2966f355858f105f4f1eca7f5635e5c9de7 100644 --- a/workspaces/ui-express/src/crop/ui/c/ConfiguredTree.tsx +++ b/workspaces/ui-express/src/crop/ui/c/ConfiguredTree.tsx @@ -6,7 +6,6 @@ interface IConfiguredTreeProps extends React.ClassAttributes { treeData: any[]; initialZoom: number; zoomable?: boolean; - onClickHandler?: any; nodeLabelComponent?: { render: React.ReactNode, foreignObjectWrapper: Record }; } diff --git a/workspaces/ui-express/src/divtree/actions/public.ts b/workspaces/ui-express/src/divtree/actions/public.ts index f145659b14df159ffcfaaec8e623222b03ce8fc7..976dfdeebb17171533e174a4dce4baec789cb508 100644 --- a/workspaces/ui-express/src/divtree/actions/public.ts +++ b/workspaces/ui-express/src/divtree/actions/public.ts @@ -4,9 +4,9 @@ // Actions import navigateTo from 'actions/navigation'; -import {filterCodeToUrl} from 'actions/filterCode'; +import { filterCodeToUrl } from 'actions/filterCode'; // import { showSnackbar } from 'actions/snackbar'; -import {createApiCaller} from 'actions/ApiCall'; +import { createApiCaller , createPureApiCaller } from 'actions/ApiCall'; // Constants import { @@ -24,6 +24,8 @@ import OpResponse from '@genesys/client/model/OpResponse'; import { apiDeleteDiversityTrees, apiRejectDiversityTrees } from 'divtree/actions/editor'; import DiversityTree from '@genesys/client/model/DiversityTree'; +import AccessionService from '../../service/genesys/AccessionService'; +import Accession from '../../model/accession/Accession'; // Wrapped API Calls const apiListDiversityTreeAccessions = createApiCaller(DiversityTreeService.listAccessions, APPEND_DIVERSITY_TREE_ACCESSIONS); const apiLoadDiversityTree = createApiCaller(DiversityTreeService.get, RECEIVE_DIVERSITY_TREE); @@ -32,6 +34,8 @@ const apiRematchAccessions = createApiCaller(DiversityTreeService.rematchAccessi const apiRejectDiversityTree = createApiCaller(DiversityTreeService.reject, RECEIVE_DIVERSITY_TREE); const apiApproveDiversityTree = createApiCaller(DiversityTreeService.approve, RECEIVE_DIVERSITY_TREE); +const apiLoadNodeAccesisons = createPureApiCaller(AccessionService.list); + export const loadMoreAccessions = (uuid: string, paged?: Page) => (dispatch, getState) => { return dispatch(apiListDiversityTreeAccessions(uuid, Page.nextPage(paged))); }; @@ -90,4 +94,12 @@ export const deleteDiversityTree = (divtree: DiversityTree) => (dispatch) => { }); }; +export const loadNodeAccessions = (uuid: string, nodeKey: string) => (dispatch) => { + const filter = { diversityTrees: [uuid], nodeKey }; + return dispatch(apiLoadNodeAccesisons(filter, { page: 0 })); +}; + +export const loadMoreNodeAccessions = (paged: FilteredPage) => (dispatch) => { + return dispatch(apiLoadNodeAccesisons(paged.filterCode, Page.nextPage(paged))); +}; diff --git a/workspaces/ui-express/src/divtree/translations.json b/workspaces/ui-express/src/divtree/translations.json index d16d5c42ff2dd8b51922f672d71929e2df95a93a..4c6c15be58f1bf7e89c98a59e2088922775531dd 100644 --- a/workspaces/ui-express/src/divtree/translations.json +++ b/workspaces/ui-express/src/divtree/translations.json @@ -24,7 +24,14 @@ "map": "Accession map", "mapDescription": "Explore diversity tree accessions on the map", "browse": "Filter accessions", - "browseDescription": "Apply custom filters to accessions in this diversity tree" + "browseDescription": "Apply custom filters to accessions in this diversity tree", + "view": "View", + "info": "Info" + }, + "treeExplorer": { + "nodeKey": "Node key: {{what}}", + "accessionList": "Accession list", + "noAccessions": "No accessions" } }, "f": { diff --git a/workspaces/ui-express/src/divtree/ui/DisplayPage.tsx b/workspaces/ui-express/src/divtree/ui/DisplayPage.tsx index 7658c989e792fa349be4bf63c5f65717d551d980..ef8ba0d5dd7bbfc81dc6da2b851e3d44f81db17f 100644 --- a/workspaces/ui-express/src/divtree/ui/DisplayPage.tsx +++ b/workspaces/ui-express/src/divtree/ui/DisplayPage.tsx @@ -13,11 +13,17 @@ import { approveDiversityTree, deleteDiversityTree, editDiversityTree, + loadNodeAccessions, + loadMoreNodeAccessions, } from 'divtree/actions/public'; import navigateTo from 'actions/navigation'; import { repositoryDownloadUrl } from 'repository/actions/public'; // import { addSubsetAccessionsToMyList } from 'list/actions/public'; // Models +import FilteredPage from 'model/FilteredPage'; +import Accession from 'model/accession/Accession'; +import RepositoryFile from 'model/repository/RepositoryFile'; +import Page from '@genesys/client/model/Page'; import DiversityTree from '@genesys/client/model/DiversityTree'; import { PublishState } from '@genesys/client/model/common.model'; import DiversityTreeAccessionRef from '@genesys/client/model/DiversityTreeAccessionRef'; @@ -26,13 +32,11 @@ import PageLayout, { PageContents } from 'ui/layout/PageLayout'; import Loading from 'ui/common/Loading'; import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton'; import PageTitle from 'ui/common/PageTitle'; -import Page from '@genesys/client/model/Page'; import ErrorMessage from 'ui/common/error/ErrorMessage'; import { ScrollToTopOnMount } from 'ui/common/page/scrollers'; import BackButton from 'ui/common/buttons/BackButton'; import DiversityTreeDisplay from './c/DiversityTreeDisplay'; import Tabs, { Tab } from 'ui/common/Tabs'; -import RepositoryFile from 'model/repository/RepositoryFile'; import TreeExplorer from 'divtree/ui/c/TreeExplorer'; const styles = () => createStyles({ @@ -71,6 +75,8 @@ interface IDisplayPageProps extends React.ClassAttributes, WithTranslation, accessionRefs: Page; currentTab?: string; repositoryDownloadUrl: (file: RepositoryFile) => string; + loadNodeAccessions: (uuid: string, nodeKey: string) => Promise>; + loadMoreNodeAccessions: (paged: Page) => Promise>; } class DisplayPage extends React.Component { @@ -91,6 +97,8 @@ class DisplayPage extends React.Component { public state = { treeData: null, treeDataLoading: false, + treeError: null, + accessions: null, }; public componentDidMount() { @@ -102,7 +110,6 @@ class DisplayPage extends React.Component { if (typeof window !== 'undefined') { if (divtree && divtree.treeFile && divtree.uuid === uuid) { - console.log('DivTree file', divtree.treeFile); // const treeFiles = cropDetails.files && cropDetails.files.filter((file) => { // return file.contentType === 'application/json' && file.originalFilename.match(/tree\.json$/); // }); @@ -126,34 +133,54 @@ class DisplayPage extends React.Component { private loadTreeFile = (treeFile: RepositoryFile) => { const { repositoryDownloadUrl } = this.props; - console.log('load divtree', treeFile); - this.setState({ treeDataLoading: true }); + this.setState({ treeDataLoading: true, treeError: null }); axios .get(repositoryDownloadUrl(treeFile)) .then((response) => { - console.log(`Tree data`, treeFile, response.data); if (typeof response.data === 'object') { this.setState({ treeData: response.data, treeDataLoading: false }); } }) - .catch((err) => { - console.log(`Error getting tree data`, err); - this.setState({ treeDataLoading: false }); + .catch((e) => { + console.log(`Error getting tree data`, e); + this.setState({ treeDataLoading: false, treeError: e }); }); }; + private loadNodeAccessions = (nodeKey: string) => { + const { loadNodeAccessions, divtree } = this.props; + + this.setState({ accessions: null }); + + loadNodeAccessions(divtree.uuid, nodeKey) + .then((data) => { + this.setState({ accessions: data }); + }) + .catch((e) => console.log('Loading accessions error: ', e)); + }; + + private loadMoreNodeAccessions = (paged: Page) => { + const { loadMoreNodeAccessions } = this.props; + + loadMoreNodeAccessions(paged) + .then((data) => { + this.setState({ accessions: Page.merge(this.state.accessions, data) }); + }) + .catch((e) => console.log('Loading accessions error: ', e)); + }; + private loadMoreAccessions = (paged: Page) => { const { loadMoreAccessions, divtree } = this.props; loadMoreAccessions(divtree.uuid, paged); - } + }; public render() { const { error, divtree, loading, userRole, t, accessionRefs, rematchDiversityTreeAccessions, publishDiversityTree, rejectDiversityTree, approveDiversityTree, deleteDiversityTree, editDiversityTree, uuid, classes, currentTab, } = this.props; - const { treeData, treeDataLoading } = this.state; + const { treeData, treeDataLoading, accessions, treeError } = this.state; const isActionsActive: boolean = userRole.findIndex((role) => role === 'ROLE_ADMINISTRATOR') !== -1 || (divtree && divtree.state === PublishState.REVIEWING); return ( @@ -175,16 +202,17 @@ class DisplayPage extends React.Component { tab={ currentTab } tabs={ [ - - { t('Info') } + + { t('divtree.public.c.diversityTreeDisplay.view') } , - - { t('View') } + + { t('divtree.public.c.diversityTreeDisplay.info') } , + ] } /> - { currentTab === 'data' && (loading ? : + { currentTab === 'info' && (loading ? :
{ error && } @@ -206,8 +234,19 @@ class DisplayPage extends React.Component {
) } - { currentTab === 'view' && (treeDataLoading ? : treeData && // todo: add error display - + { currentTab === 'view' && ( + <> + { treeDataLoading && } + { treeData && + + } + { treeError &&
} + ) } ); @@ -221,7 +260,7 @@ const mapStateToProps = (state, ownProps) => ({ userRole: state.login.authorities, error: state.divtree.public.divtree ? state.divtree.public.divtree.error : undefined, uuid: ownProps.match.params.uuid, - currentTab: ownProps.match.params.tab || 'data', + currentTab: ownProps.match.params.tab || 'view', }); const mapDispatchToProps = (dispatch) => bindActionCreators({ @@ -234,6 +273,8 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({ loadMoreAccessions, rematchDiversityTreeAccessions, repositoryDownloadUrl, + loadNodeAccessions, + loadMoreNodeAccessions, // addDiversityTreeAccessionsToMyList, }, dispatch);