From d49d7d34a83ebb4b07391b301056f12e9cc70f6e Mon Sep 17 00:00:00 2001 From: Oleksii Savran Date: Wed, 2 Sep 2020 19:18:07 +0300 Subject: [PATCH] Diversity trees: Public pages --- src/model/genesys/CropDetails.ts | 26 ++++ .../ui-express/src/crop/ui/DisplayPage.tsx | 74 +++------- .../src/crop/ui/c/ConfiguredTree.tsx | 82 +++++++++-- workspaces/ui-express/src/divtree/routes.ts | 2 +- .../ui-express/src/divtree/ui/DisplayPage.tsx | 127 ++++++++++++++++-- .../ui-express/src/ui/layout/PageLayout.tsx | 5 +- 6 files changed, 230 insertions(+), 86 deletions(-) create mode 100644 src/model/genesys/CropDetails.ts diff --git a/src/model/genesys/CropDetails.ts b/src/model/genesys/CropDetails.ts new file mode 100644 index 00000000..569d99d3 --- /dev/null +++ b/src/model/genesys/CropDetails.ts @@ -0,0 +1,26 @@ +import Article from 'model/cms/Article'; +import Crop from 'model/genesys/Crop'; +import ImageGallery from 'model/repository/ImageGallery'; +import RepositoryFile from 'model/repository/RepositoryFile'; +import Dataset from 'model/catalog/Dataset'; +import Subset from 'model/subset/Subset'; +import DescriptorList from 'model/catalog/DescriptorList'; +import DiversityTree from 'model/DiversityTree'; + +/* +* Defined in Swagger as '#/definitions/CropDetails' +*/ +class CropDetails extends Crop { + public accessionCount: number; + public blurb: Article; + public files: RepositoryFile[]; + public imageGallery: ImageGallery; + public overview: any; + public recentDatasets: Dataset[]; + public recentSubsets: Subset[]; + public recentDescriptorLists: DescriptorList[]; + public diversityTrees: DiversityTree[]; + +} + +export default CropDetails; diff --git a/workspaces/ui-express/src/crop/ui/DisplayPage.tsx b/workspaces/ui-express/src/crop/ui/DisplayPage.tsx index 4fc88ce0..e13c0ea2 100644 --- a/workspaces/ui-express/src/crop/ui/DisplayPage.tsx +++ b/workspaces/ui-express/src/crop/ui/DisplayPage.tsx @@ -4,11 +4,9 @@ import { bindActionCreators } from 'redux'; import { WithTranslation, withTranslation } from 'react-i18next'; import withStyles from '@material-ui/core/styles/withStyles'; import * as _ from 'lodash'; -import axios from 'axios'; // Actions import { loadCropDetails } from 'crop/actions/public'; -import { repositoryDownloadUrl } from 'repository/actions/public'; import { relinkAccessions } from 'crop/actions/admin'; import { applyFiltersAsync as applyFilters, applyOverviewFiltersAsync as applyOverviewFilters } from 'accessions/actions/public'; import { showSnackbar } from 'actions/snackbar'; @@ -38,9 +36,9 @@ import { IPageRequest } from '@genesys/client/model/Page'; import ErrorMessage from 'ui/common/error/ErrorMessage'; import ImageGalleryView from 'repository/ui/c/ImageGalleryView'; import TreePreviewer from 'crop/ui/c/TreePreviewer'; -import RepositoryFile from '@genesys/client/model/repository/RepositoryFile'; +// import RepositoryFile from 'model/repository/RepositoryFile'; import { ScrollToTopOnMount } from 'ui/common/page/scrollers'; -import { DatasetLink, SubsetLink } from 'ui/genesys/Links'; +import { DatasetLink, DiversityTreeLink, SubsetLink } from 'ui/genesys/Links'; import { DescriptorListLink } from 'ui/catalog/Links'; import { Grid } from '@material-ui/core'; @@ -67,8 +65,6 @@ interface IDisplayPageProps extends React.ClassAttributes, WithTranslation relinkAccessions: (shortName: string) => void; applyOverviewFilters: (filters: string | AccessionFilter, page?: IPageRequest) => void; showSnackbar: (snack: string) => void; - repositoryDownloadUrl: (file: RepositoryFile) => string; - treeData: object; } class DisplayPage extends React.Component { @@ -79,56 +75,12 @@ class DisplayPage extends React.Component { }, ]; - public state: { - treesData: {treeData: any, treeFile: any}[], - } = { - treesData: null, - }; - public componentDidMount() { - const { loadCropDetails, shortName, cropDetails, repositoryDownloadUrl } = this.props; + const { loadCropDetails, shortName, cropDetails } = this.props; if (shortName && (! cropDetails || shortName !== cropDetails.shortName)) { loadCropDetails(shortName); this.setState({ treesData: null }); } - - if (typeof window !== 'undefined') { - if (cropDetails && cropDetails.files && cropDetails.shortName === shortName) { - console.log(`Crop files`, cropDetails.files); - const treeFiles = cropDetails.files && cropDetails.files.filter((file) => { - return file.contentType === 'application/json' && file.originalFilename.match(/tree\.json$/); - }); - if (treeFiles && treeFiles.length > 0) { - const treeFilePromises = treeFiles.map((treeFile) => axios.get(repositoryDownloadUrl(treeFile)) - .then((response) => ({ treeData: response.data, treeFile })) - .catch((err) => { console.log(`Err`, err)})); - Promise.all(treeFilePromises) - .then((treesData) => this.setState({treesData: treesData.filter((treeData) => !!treeData)})) - .catch((err) => { console.log(`Error getting tree data`, err)}); - } - } - } - - } - - public componentDidUpdate(prevProps: IDisplayPageProps) { - const { cropDetails: currentCrop } = prevProps; - const { cropDetails, repositoryDownloadUrl } = this.props; - const { treesData } = this.state; - if (cropDetails && (! currentCrop || (currentCrop.shortName !== cropDetails.shortName))) { - console.log(`Crop files`, cropDetails.files); - const treeFiles = cropDetails.files && cropDetails.files.filter((file) => { - return file.contentType === 'application/json' && file.originalFilename.match(/tree\.json$/); - }); - if (treeFiles && treeFiles.length > 0 && ! treesData) { - const treeFilePromises = treeFiles.map((treeFile) => axios.get(repositoryDownloadUrl(treeFile)) - .then((response) => ({ treeData: response.data, treeFile })) - .catch((err) => { console.log(`Err`, err)})); - Promise.all(treeFilePromises) - .then((treesData) => this.setState({treesData: treesData.filter((treeData) => !!treeData)})) - .catch((err) => { console.log(`Error getting tree data`, err)}); - } - } } private applyCropFilter = () => { @@ -172,7 +124,6 @@ class DisplayPage extends React.Component { public render() { const { cropDetails, error, shortName, classes, t } = this.props; - const { treesData } = this.state; const crop = cropDetails; return ( @@ -256,9 +207,6 @@ class DisplayPage extends React.Component { propertyItemProps={ {classes: {propertiesRowLabel: classes.italicProperties} } } /> } - { treesData && treesData.map((tree) => (tree && tree.treeData.children && tree.treeData.children.length > 0 && - - ))} } { /> } + { cropDetails.diversityTrees && cropDetails.diversityTrees.length > 0 && + + ( + { value: } + )) + } + /> + + } } @@ -335,7 +298,6 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({ relinkAccessions, applyOverviewFilters, showSnackbar, - repositoryDownloadUrl, }, dispatch); diff --git a/workspaces/ui-express/src/crop/ui/c/ConfiguredTree.tsx b/workspaces/ui-express/src/crop/ui/c/ConfiguredTree.tsx index d4c61829..dbb1eb6d 100644 --- a/workspaces/ui-express/src/crop/ui/c/ConfiguredTree.tsx +++ b/workspaces/ui-express/src/crop/ui/c/ConfiguredTree.tsx @@ -1,4 +1,10 @@ import * as React from 'react'; +// import { createStyles, withStyles } from '@material-ui/core/styles'; +// +// const styles = createStyles({ +// leafNode: {}, +// node: {}, +// }); let Tree; @@ -6,21 +12,59 @@ interface IConfiguredTreeProps extends React.ClassAttributes { treeData: any[]; initialZoom: number; zoomable?: boolean; + onClickHandler?: any; +} + +class NodeLabel extends React.PureComponent { + private timeout: any = null; + public state = { + count: 0, + }; + + private onClick = (e) => { + const { nodeData } = this.props; + if (this.state.count === 1) { + console.log('expand!'); + clearTimeout(this.timeout); + this.setState({ count: 0 }); + return; + } + + e.stopPropagation(); + this.setState({ count: 1 }); + + this.timeout = setTimeout(() => { + console.log('custom handler!', nodeData); + this.setState({ count: 0 }); + }, 300); + }; + + public render() { + const { nodeData } = this.props; + return ( +
+
{ nodeData.name }
+
+ ) + } } class ConfiguredTree extends React.Component { private treeContainer = null; + // private timeout: any = null; public constructor(props, context) { super(props, context); if (typeof window !== 'undefined') { Tree = require('react-d3-tree').Tree; } - this.state = { - translate: null, - }; } + public state = { + count: 0, + translate: null, + }; + public componentDidMount() { if (this.treeContainer) { const dimensions = this.treeContainer.getBoundingClientRect(); @@ -34,12 +78,13 @@ class ConfiguredTree extends React.Component { } public render() { - const {treeData, initialZoom, zoomable = false} = this.props; + const { treeData, initialZoom, zoomable = false } = this.props; return ( -
this.treeContainer = ref }> +
this.treeContainer = ref } /* onClickCapture={ this.test } */> { treeData && typeof window !== 'undefined' && { circle: { fill: '#88ba42', }, - name: { - strokeWidth: 0.4, - transform: 'translate(-13px, -1px)', - textAnchor: 'end', - }, + // name: { + // strokeWidth: 0.4, + // transform: 'translate(-13px, -1px)', + // textAnchor: 'end', + // }, }, leafNode: { circle: { fill: '#777777', }, - name: { - strokeWidth: 0.4, - transform: 'translate(13px)', - }, + // name: { + // strokeWidth: 0.4, + // transform: 'translate(13px)', + // }, }, }, } } @@ -85,6 +130,15 @@ class ConfiguredTree extends React.Component { textAnchor: 'start', y: 0, } } + allowForeignObjects + nodeLabelComponent={{ + render: , + foreignObjectWrapper: { + y: -6, + x: -10, + height: '1.1rem', + }, + }} /> }
diff --git a/workspaces/ui-express/src/divtree/routes.ts b/workspaces/ui-express/src/divtree/routes.ts index 427c54bb..ffd8748c 100644 --- a/workspaces/ui-express/src/divtree/routes.ts +++ b/workspaces/ui-express/src/divtree/routes.ts @@ -17,7 +17,7 @@ const publicRoutes = [ // }, // }, { - path: '/divtree/:uuid([a-z\\-0-9]+)', + path: '/divtree/:uuid([a-z\\-0-9]+)/:tab?', component: Loadable({ loader: () => import(/* webpackMode:"lazy", webpackChunkName: "divtree" */'divtree/ui/DisplayPage'), }), diff --git a/workspaces/ui-express/src/divtree/ui/DisplayPage.tsx b/workspaces/ui-express/src/divtree/ui/DisplayPage.tsx index d611ddbf..7133d08b 100644 --- a/workspaces/ui-express/src/divtree/ui/DisplayPage.tsx +++ b/workspaces/ui-express/src/divtree/ui/DisplayPage.tsx @@ -2,6 +2,8 @@ import * as React from 'react'; import { WithTranslation, withTranslation } from 'react-i18next'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; +import axios from 'axios'; +import { createStyles, WithStyles, withStyles } from '@material-ui/core/styles'; // Actions import { rematchDiversityTreeAccessions, @@ -13,6 +15,7 @@ import { editDiversityTree, } from 'divtree/actions/public'; import navigateTo from 'actions/navigation'; +import { repositoryDownloadUrl } from 'repository/actions/public'; // import { addSubsetAccessionsToMyList } from 'list/actions/public'; // Models import DiversityTree from '@genesys/client/model/DiversityTree'; @@ -28,8 +31,37 @@ 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 ConfiguredTree from 'crop/ui/c/ConfiguredTree'; -interface IDisplayPageProps extends React.ClassAttributes, WithTranslation { +const styles = () => createStyles({ + layoutOverrides: { + '& > div': { + '& > div:first-child': { + 'flexShrink': 0, + 'flexGrow': 2, + 'height': '100%', + 'display': 'flex', + 'flexDirection': 'column', + '& > div:first-child': { + flexBasis: 'auto', + }, + }, + }, + }, + treeWrapper: { + 'direction': 'ltr', + 'fontSize': '1.2rem', + 'flexGrow': 2, + 'display': 'flex', + '& > div': { + flexGrow: 2, + }, + }, +}); + +interface IDisplayPageProps extends React.ClassAttributes, WithTranslation, WithStyles { uuid: string; divtree: DiversityTree; userRole: string[]; @@ -46,6 +78,8 @@ interface IDisplayPageProps extends React.ClassAttributes, WithTranslation rematchDiversityTreeAccessions: (divtree: DiversityTree) => void; addDiversityTreeAccessionsToMyList: (divtree: DiversityTree) => void; accessionRefs: Page; + currentTab?: string; + repositoryDownloadUrl: (file: RepositoryFile) => string; } class DisplayPage extends React.Component { @@ -63,33 +97,76 @@ class DisplayPage extends React.Component { super(props, context); } + public state = { + treeData: null, + treeDataLoading: false, + }; + public componentDidMount() { const { divtree, uuid, loadDiversityTree, loadMoreAccessions } = this.props; if (uuid && (! divtree || uuid !== divtree.uuid)) { loadDiversityTree(uuid); loadMoreAccessions(uuid); } + + 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$/); + // }); + this.loadTreeFile(divtree.treeFile); + } + } } - public componentDidUpdate() { + public componentDidUpdate(prevProps: IDisplayPageProps) { const { loading, divtree, uuid, loadDiversityTree, loadMoreAccessions } = this.props; + const { divtree: prevDivTree } = prevProps; + if (!loading && uuid && (! divtree || uuid !== divtree.uuid)) { loadDiversityTree(uuid); loadMoreAccessions(uuid); } + + if (divtree && (! prevDivTree || (prevDivTree.uuid !== divtree.uuid))) { + this.loadTreeFile(divtree.treeFile); + } } + private loadTreeFile = (treeFile: RepositoryFile) => { + const { repositoryDownloadUrl } = this.props; + console.log('load divtree', treeFile); + + this.setState({ treeDataLoading: true }); + 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 }); + }); + }; + 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 } = this.props; + const { + error, divtree, loading, userRole, t, accessionRefs, rematchDiversityTreeAccessions, publishDiversityTree, + rejectDiversityTree, approveDiversityTree, deleteDiversityTree, editDiversityTree, uuid, classes, currentTab, + } = this.props; + const { treeData, treeDataLoading } = this.state; const isActionsActive: boolean = userRole.findIndex((role) => role === 'ROLE_ADMINISTRATOR') !== -1 || (divtree && divtree.state === PublishState.REVIEWING); return ( - - + { /> } /> - - - { loading ? : + + { t('Info') } + , + + { t('View') } + , + ] + } + /> + { currentTab === 'data' && (loading ? : +
{ error && } { divtree && - { approveDiversityTree={ approveDiversityTree } deleteDiversityTree={ deleteDiversityTree } addAllToMyList={ this.props.addDiversityTreeAccessionsToMyList } - /> + /> }
- } -
+
+ ) } + { currentTab === 'view' && (treeDataLoading ? : treeData && +
+ +
+ ) }
); } @@ -138,6 +237,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', }); const mapDispatchToProps = (dispatch) => bindActionCreators({ @@ -149,8 +249,9 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({ deleteDiversityTree, loadMoreAccessions, rematchDiversityTreeAccessions, + repositoryDownloadUrl, // addDiversityTreeAccessionsToMyList, }, dispatch); -export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(DisplayPage)); +export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(withTranslation()(DisplayPage))); diff --git a/workspaces/ui-express/src/ui/layout/PageLayout.tsx b/workspaces/ui-express/src/ui/layout/PageLayout.tsx index b8cfea8d..e384eec2 100644 --- a/workspaces/ui-express/src/ui/layout/PageLayout.tsx +++ b/workspaces/ui-express/src/ui/layout/PageLayout.tsx @@ -67,13 +67,14 @@ interface ILayoutProps extends React.ClassAttributes, WithStyles { children?: any; sidebar?: any; withFooter?: any; + className?: string; } -function Layout({classes, children = null, sidebar = null, withFooter = false}: ILayoutProps) { +function Layout({classes, children = null, sidebar = null, withFooter = false, className = ''}: ILayoutProps) { const sidebarIsOpen = useSelector((state) => state.user.public.sidebarOpen) || false; return (
-
+
{ sidebar && } { children && (
-- GitLab