From 3d736414fd29614b4f83f93f81372214201d54d9 Mon Sep 17 00:00:00 2001 From: Philip Adenekan Date: Fri, 6 Aug 2021 09:48:26 -0700 Subject: [PATCH] ENCD-1917-redesign-first-page-ui --- src/encoded/new_home_page.py | 26 + src/encoded/static/components/index.js | 1 + .../static/components/matrix_chip_seq.js | 1209 ++++------------- .../static/components/new_home_page.js | 12 + .../scss/encoded/modules/_new_home_page.scss | 267 ++++ src/encoded/static/scss/style.scss | 1 + 6 files changed, 593 insertions(+), 923 deletions(-) create mode 100644 src/encoded/new_home_page.py create mode 100644 src/encoded/static/components/new_home_page.js create mode 100644 src/encoded/static/scss/encoded/modules/_new_home_page.scss diff --git a/src/encoded/new_home_page.py b/src/encoded/new_home_page.py new file mode 100644 index 00000000000..498f20bc0d8 --- /dev/null +++ b/src/encoded/new_home_page.py @@ -0,0 +1,26 @@ +from pyramid.view import view_config +from encoded.vis_defines import vis_format_url +from snovault import TYPES +from .batch_download import get_peak_metadata_links +from collections import OrderedDict +import requests +from urllib.parse import urlencode + +def includeme(config): + config.add_route('new-home-page', '/new-home-page{slash:/?}') + config.scan(__name__) + +@view_config(route_name='new-home-page', request_method='GET', permission='search') +def new_home_page(context, request): + result = { + '@id': '/region-search/' + ('?' + request.query_string.split('&referrer')[0] if request.query_string else ''), + '@type': ['new-home-page'], + 'title': 'Search by region', + 'facets': [], + '@graph': [], + 'columns': OrderedDict(), + 'notification': '', + 'filters': [] + } + + return result diff --git a/src/encoded/static/components/index.js b/src/encoded/static/components/index.js index 5689f06bf97..715bac4a3fd 100644 --- a/src/encoded/static/components/index.js +++ b/src/encoded/static/components/index.js @@ -30,6 +30,7 @@ require('./report'); require('./rnaget'); require('./matrix_audit'); require('./matrix_entex'); +require('./new_home_page'); require('./matrix_brain'); require('./matrix_mouse_development'); require('./matrix_experiment'); diff --git a/src/encoded/static/components/matrix_chip_seq.js b/src/encoded/static/components/matrix_chip_seq.js index 0d2c7893404..15aecd878f3 100644 --- a/src/encoded/static/components/matrix_chip_seq.js +++ b/src/encoded/static/components/matrix_chip_seq.js @@ -1,920 +1,292 @@ import React from 'react'; import PropTypes from 'prop-types'; -import pluralize from 'pluralize'; -import url from 'url'; -import PubSub from 'pubsub-js'; -import _ from 'underscore'; -import QueryString from '../libs/query_string'; -import { Panel, PanelBody, TabPanelPane } from '../libs/ui/panel'; -import { Modal, ModalHeader, ModalBody } from '../libs/ui/modal'; -import { svgIcon } from '../libs/svg-icons'; import * as globals from './globals'; -import { MatrixBadges, DisplayAsJson, useMount } from './objectutils'; -import { SearchFilter } from './matrix'; -import { TextFilter, FacetList, ClearFilters } from './search'; -import { DivTable } from './datatable'; +import NavBarMultiSearch from './top_hits/multi/search'; +import Tooltip from '../libs/ui/tooltip'; +const EXPANDED_CARD_SIZE = 4; -const SEARCH_PERFORMED_PUBSUB = 'searchPerformed'; -const CLEAR_SEARCH_BOX_PUBSUB = 'clearSearchBox'; -const FACET_SESSION_SUFFIX = 'chip_seq_facet_session_'; -const displayedFacets = ['target.investigated_as', 'perturbed']; - -/** - * Transform context to a form where easier to fetch information - * - * @param {context} context - Context from react - * @param {string} assayTitle - Assay Title - * @param {string} organismName - Organism Name - * @returns {object} - Object with structure - { chIPSeqData, subTabs }; - * - * chIPSeqData is an object where: - * key is a sub Tab - * value is of structure - { headerRow, dataRow, assayTitle, organismName }; - * headerRow - header content-list - * dataRow - array of non-header content - * key: biosample ontology classification - * value: array containing counts - * assayTitle - Assay title - * organismName- Organism name - * subTabs: List of subTabs for easy access - */ -const getChIPSeqData = (context, assayTitle, organismName) => { - if (!context || !context.matrix || !context.matrix.x || !context.matrix.y || !assayTitle || !organismName) { - return null; - } - - const subTabSource = 'biosample_ontology.classification'; - - const subTabs = context.matrix.x[subTabSource].buckets.map((x) => x.key); - const chIPSeqData = {}; - - subTabs.forEach((subTab) => { - const xGroupBy1 = context.matrix.x.group_by[0]; - const xGroupBy2 = context.matrix.x.group_by[1]; - const headerRow = context.matrix.x[xGroupBy1].buckets.find((f) => f.key === subTab)[xGroupBy2] - .buckets - .reduce((a, b) => a.concat(b), []) - .map((x) => x.key); - const headerRowIndex = headerRow.reduce((x, y, z) => { x[y] = z; return x; }, []); - const headerRowLength = headerRow.length; - const yGroupBy1 = context.matrix.y.group_by[0]; - const yGroupBy2 = context.matrix.y.group_by[1]; - - const yData = context.matrix.y[yGroupBy1].buckets - .find((rBucket) => rBucket.key === organismName)[yGroupBy2].buckets - .reduce((a, b) => { - const m = {}; - m[b.key] = b[xGroupBy1].buckets - .filter((f) => f.key === subTab) - .reduce((x, y) => { - x.push([...y[xGroupBy2].buckets] - .reduce((i, j) => i.concat(j), [])); - return x; - }, []); - return a.concat(m); - }, []); - - const dataRowT = {}; - - yData.forEach((y) => { - const yKey = Object.keys(y)[0]; - dataRowT[yKey] = dataRowT[yKey] || Array(headerRowLength + 1).fill({ content: 0, proteinTagStatus: 'no_tagged_protein' }); - dataRowT[yKey][0] = { content: yKey }; - - const keyDocCountPair = y[yKey].reduce((a, b) => a.concat(b), []); - - keyDocCountPair.forEach((kp) => { - const { key } = kp; - const docCount = kp.doc_count; - const index = headerRowIndex[key]; - const kpProteinBucket = kp['protein_tags.name'].buckets.map((b) => b.key); - - const getProteinTagStatus = (proteinTagBucket) => { - const separatedProteinBucket = proteinTagBucket.reduce((acc, cv) => { - if (cv === 'no_protein_tags') { - acc.noTaggedProteinCount += 1; - } else { - acc.taggedProteinCount += 1; - } - return acc; - }, { taggedProteinCount: 0, noTaggedProteinCount: 0 }); - - if (separatedProteinBucket.noTaggedProteinCount === 0) { - return 'all_proteins_tagged'; - } - - if (separatedProteinBucket.taggedProteinCount === 0) { - return 'no_tagged_protein'; - } - - return 'mixed'; - }; - - dataRowT[yKey][index + 1] = { - content: docCount, - proteinTagStatus: getProteinTagStatus(kpProteinBucket), - }; - }); - }); - - let dataRow = []; - const keys = Object.keys(dataRowT); - - // move biosample ontology classifications to dataRow-group - keys.forEach((key) => { - dataRow.push(dataRowT[key]); - }); - - // remove all rows with all 0's - // Note- First entry is biosample ontology classification, does not count against 0's-row and is weedy out in the statement - dataRow = dataRow.filter((data) => data.some((content, index) => (content !== 0 && index !== 0))); - - chIPSeqData[subTab] = { headerRow, dataRow, assayTitle, organismName }; - }); - - const subTabsSorted = subTabs.sort(); - - // whole organisms should be moved to the front - return { - chIPSeqData, - subTabs: [ - subTabsSorted.find((item) => item === 'whole organisms'), - ...subTabsSorted.filter((tab) => tab !== 'whole organisms'), - ].filter((tab) => tab !== undefined), - }; -}; - -/** - * Transform chIP Seq data to a form DataTable-object can understand. - * - * @param {chIPSeqData} chIPSeqData - * @param {string} selectedTabLevel3 - Sub tab to use - * @returns {object} DataTable-ready structure. - */ -const convertTargetDataToTable = (chIPSeqData, selectedTabLevel3, context) => { - if (!chIPSeqData || !chIPSeqData.headerRow || !chIPSeqData.dataRow) { - return []; - } - - // add assay_title = Mint chip-seq if the assay selected in Histone chip-seq - const isAssayTitleHistone = chIPSeqData.assayTitle === 'Histone ChIP-seq'; - const removeSpecialCharacters = (name) => (!name ? name : name.replace(/\s/g, '')); - const headerQuery = new QueryString(context.search_base); - - if (isAssayTitleHistone && !headerQuery.getKeyValues('assay_title').includes('Mint-ChIP-seq')) { - headerQuery.addKeyValue('assay_title', 'Mint-ChIP-seq'); - } - - const headerRow = [ - { - id: 'header00', - content: '\u00A0', - style: {}, - className: 'div-table-matrix__row__header-item', +const newPageData = [ + { + id: '1', + header: { + text: 'Encylopedia1', + url: 'http://www.google.com', }, - ...chIPSeqData.headerRow.map((x) => ({ - id: removeSpecialCharacters(`${x}`), - content: {x}, - className: 'div-table-matrix__row__header-item', - style: {}, - })), - ]; - - const rowLength = chIPSeqData.dataRow.length > 0 ? chIPSeqData.dataRow[0].length : 0; - - const rowData = chIPSeqData.dataRow.map((row, rIndex) => { - const rowContent = row.map((y, yIndex) => { - let content; - const yQuery = new QueryString(context.search_base); - - yQuery.addKeyValue('target.label', row[0].content); - yQuery.addKeyValue('biosample_ontology.classification', selectedTabLevel3); - - if (isAssayTitleHistone && !yQuery.getKeyValues('assay_title').includes('Mint-ChIP-seq')) { - yQuery.addKeyValue('assay_title', 'Mint-ChIP-seq'); - } - - if (yIndex === 0) { - const borderLeft = '1px solid #fff'; // make left-most side border white - - content = { - id: removeSpecialCharacters(`${y}`), - content: {y.content}, - style: { borderLeft }, - className: 'div-table-matrix__row__data-row-item', - }; - } else { - const borderTop = rIndex === 0 ? '1px solid #f0f0f0' : ''; // add border color to topmost rows - const primaryBoxColor = '#5064c8'; - const proteinColor = '#b4c8ff'; - let backgroundColor; // determined if box is colored or not - let backgroundImage; - const borderRight = yIndex === rowLength - 1 ? '1px solid #f0f0f0' : ''; // add border color to right-most rows - - yQuery.addKeyValue('biosample_ontology.term_name', chIPSeqData.headerRow[yIndex - 1]); - - backgroundColor = y.content === 0 ? '#fff' : primaryBoxColor; // determined if box is colored or not - - if (y.content === 0) { - backgroundColor = '#fff'; - } else if (y.proteinTagStatus === 'all_proteins_tagged') { - backgroundColor = proteinColor; - } else if (y.proteinTagStatus === 'no_tagged_protein') { - backgroundColor = primaryBoxColor; - } else { - backgroundImage = `linear-gradient(135deg, ${primaryBoxColor} 50%, ${proteinColor} 50%)`; - } - - let proteinTitle = ' '; - - if (y.content === 0) { - proteinTitle = ''; - } else if (y.proteinTagStatus === 'all_proteins_tagged') { - proteinTitle = ' with all proteins tagged'; - } else if (y.proteinTagStatus === 'mixed') { - proteinTitle = ' with some proteins tagged'; - } else { - proteinTitle = ' with no protein tagged'; - } - - content = { - id: removeSpecialCharacters(`${row[0].content}${chIPSeqData.headerRow[yIndex - 1]}`), - content:  , - style: { backgroundColor, borderTop, borderRight, backgroundImage }, - className: 'div-table-matrix__row__data-row-item', - }; - } - return content; - }); - - return rowContent; - }); - - const chIpSeqTable = [headerRow, ...rowData]; - return chIpSeqTable; -}; - -/** -* First row of Tab- Organism -* -* id: Server name -* header: Name shown to user -* headerImage: Image shown beside header -* url: hyperlink (set by array's consumer) -*/ -const tabLevel1 = [ + url: 'search/?type=Experiment&control_type!=*&status=released&perturbed=false', + content: 'Lorem ipsum1', + footer: 'Lorem ipsum footer', + }, { - id: 'Homo sapiens', - header: 'Homo sapiens', - headerImage: '/static/img/bodyMap/organisms/Homo-sapiens.svg', - url: '', + id: '2', + header: { + text: 'Encylopedia2', + url: 'http://www.google.com', + }, + url: 'search/?type=Experiment&control_type!=*&status=released&perturbed=false', + content: 'Lorem ipsum2', + footer: 'Lorem ipsum footer', }, { - id: 'Mus musculus', - header: 'Mus musculus', - headerImage: '/static/img/bodyMap/organisms/Mus-musculus.svg', - url: '', + id: '3', + header: { + text: 'Encylopedia3', + url: 'http://www.google.com', + }, + url: 'search/?type=Experiment&control_type!=*&status=released&perturbed=false', + content: 'Lorem ipsum3', + footer: 'Lorem ipsum footer', }, { - id: 'Caenorhabditis elegans', - header: 'Caenorhabditis elegans', - headerImage: '/static/img/bodyMap/organisms/Caenorhabditis-elegans.svg', - url: '', + id: '4', + header: { + text: 'Encylopedia4', + url: 'http://www.google.com', + }, + url: 'search/?type=Experiment&control_type!=*&status=released&perturbed=false', + content: 'Lorem ipsum4', + footer: 'Lorem ipsum footer', }, { - id: 'Drosophila melanogaster', - header: 'Drosophila melanogaster', - headerImage: '/static/img/bodyMap/organisms/Drosophila-melanogaster.svg', - url: '', + id: '5', + header: { + text: 'Single cell5', + url: 'http://www.google.com', + }, + url: 'search/?type=Experiment&control_type!=*&status=released&perturbed=false', + content: 'Lorem ipsum5', + footer: 'Lorem ipsum footer', }, -]; - -/** - * Second tab- Assay title - * - * id: Server name - * header: Name shown to user - * url: hyperlink (set by array's consumer) - */ -const tabLevel2 = [ { - id: 'Histone ChIP-seq', - header: 'Histone', - url: '', + id: '6', + header: { + text: 'FCC6', + url: 'http://www.google.com', + }, + url: 'search/?type=Experiment&control_type!=*&status=released&perturbed=false', + content: 'Lorem ipsum6', + footer: 'Lorem ipsum footer', + }, + { + id: '7', + header: { + text: 'Reference Epigenome7', + url: 'http://www.google.com', + }, + url: 'search/?type=Experiment&control_type!=*&status=released&perturbed=false', + content: 'Lorem ipsum7', + footer: 'Lorem ipsum footer', }, { - id: 'TF ChIP-seq', - header: 'Transcription Factor', - url: '', + id: '8', + header: { + text: 'Experiments8', + url: 'http://www.google.com', + }, + url: 'search/?type=Experiment&control_type!=*&status=released&perturbed=false', + content: 'Lorem ipsum8', + footer: 'Lorem ipsum footer', + }, + { + id: '9', + header: { + text: 'Annotation9', + url: 'http://www.google.com', + }, + url: 'search/?type=Experiment&control_type!=*&status=released&perturbed=false', + content: 'Lorem ipsum9', + footer: 'Lorem ipsum footer', + }, + { + id: '10', + header: { + text: 'Biosample10', + url: 'http://www.google.com', + }, + url: 'search/?type=Experiment&control_type!=*&status=released&perturbed=false', + content: 'Lorem ipsum10', + footer: 'Lorem ipsum footer', + }, + { + id: '11', + header: { + text: 'Matrix11', + url: 'http://www.google.com', + }, + url: 'search/?type=Experiment&control_type!=*&status=released&perturbed=false', + content: 'Lorem ipsum11', + footer: 'Lorem ipsum footer', }, ]; - -const assayTitlesOptions = tabLevel2.map((tab) => tab.id); - -/** - * Files (Drosophila melanogaster) and worms (Caenorhabditis elegans) exclude the Histone tab - */ -const organismsWithHiddenHistoneAssay = ['Drosophila melanogaster', 'Caenorhabditis elegans']; - - -const Spinner = ({ isActive }) => ( - <> - {isActive ? -
-
-
-
-
- : null} - +const Card = ({ item }) => ( +
+
{item.header.text}
+
{item.content}
+
{item.footer}
+
); -Spinner.propTypes = { - isActive: PropTypes.bool, -}; - -Spinner.defaultProps = { - isActive: false, +Card.propTypes = { + item: PropTypes.object.isRequired, }; - -/** - * ChIP-Seq Matrix text filter. - * - * Important to extend TextFilter because this class has functionality it lacks like - * knowing when to clear text book via Pubsub subscription - * - * @class ChIPSeqMatrixTextFilter - * @extends {TextFilter} - */ -class ChIPSeqMatrixTextFilter extends TextFilter { - constructor() { - super(); - - this.handleChange = this.handleChange.bind(this); - this.clearSearch = this.clearSearch.bind(this); - - this.state = { searchOption: 'biosample' }; - this.searchBox = React.createRef(); - } - - componentDidMount() { - this.clearSearchPubSub = PubSub.subscribe(CLEAR_SEARCH_BOX_PUBSUB, this.clearSearch); - } - - componentWillUnmount() { - PubSub.unsubscribe(this.clearSearchPubSub); - } - - clearSearch() { - if (this.searchBox && this.searchBox.current) { - this.searchBox.current.value = ''; - } - } - - onKeyDown(e) { - if (e.keyCode === 13) { - e.preventDefault(); - PubSub.publish(SEARCH_PERFORMED_PUBSUB, { - text: e.target.value, - option: this.state.searchOption, - }); - } - } - - handleChange(e) { - this.setState({ searchOption: e.target.value }); - } - - render() { - const filterText = this.state.searchOption === 'biosample' ? - 'Enter any text string such as lung or musc or H9 to filter biosample' : - 'Enter any text string such as ac or H3 to filter ChIP target'; - - return ( -
- - -
- ); - } -} - -/** - * Hold code and markup for search - * - * SearchFilter extended rather than used because this class has function is does not like use - * of ChIPSeqMatrixTextFilter - * - * @class ChIPSeqMatrixSearch - * @extends {SearchFilter} - */ -class ChIPSeqMatrixSearch extends SearchFilter { - render() { - const { context } = this.props; - const parsedUrl = url.parse(this.context.location_href); - const matrixBase = parsedUrl.search || ''; - const matrixSearch = matrixBase + (matrixBase ? '&' : '?'); - const parsed = url.parse(matrixBase, true); - const queryStringType = parsed.query.type || ''; - const type = pluralize(queryStringType.toLocaleLowerCase()); - return ( -
-

Enter filter terms to filter the {type} included in the matrix.

-
- -
- -
-
+const ClosedPlate = ({ item }) => ( +
+
+ - ); - } -} - -/** -* Render the area above the matrix itself, including the page title. -*/ -const ChIPSeqMatrixHeader = (props) => { - const [context] = React.useState(props.context); - - return ( -
-
-
-

{context.title}

- -
-
-
-
- -
-
-
-
 
-
- -
-
-
-
-
- ); -}; - -ChIPSeqMatrixHeader.propTypes = { - context: PropTypes.object.isRequired, -}; - -const ChIPSeqMatrixContent = ({ context }) => ( -
-
); -ChIPSeqMatrixContent.propTypes = { - context: PropTypes.object.isRequired, +ClosedPlate.propTypes = { + item: PropTypes.object.isRequired, }; -/** - * Component for creating tab-markup. - * - * @class ChIPSeqTabPanel - * @extends {React.Component} - */ -class ChIPSeqTabPanel extends React.Component { - render() { - const { tabList, navCss, moreComponents, moreComponentsClasses, tabFlange, decoration, decorationClasses, selectedTab, handleTabClick, fontColors } = this.props; - let children = []; - let firstPaneIndex = -1; // React.Children.map index of first component - - // We expect to find child elements inside . For any we find, get - // the React `key` value and copy it to an `id` value that we add to each child component. - // That lets each child get an HTML ID matching `key` without having to pass both a key and - // id with the same value. We also set the `active` property in the TabPanelPane component - // here too so that each pane knows whether it's the active one or not. ### React14 - if (this.props.children) { - children = React.Children.map(this.props.children, (child, i) => { - if (child.type === TabPanelPane) { - firstPaneIndex = firstPaneIndex === -1 ? i : firstPaneIndex; - - // Replace the existing child component - const active = this.getCurrentTab() === child.key; - return React.cloneElement(child, { id: child.key, active }); - } - return child; - }); - } - - const baseUrl = '/chip-seq-matrix/?type=Experiment'; - - return ( -
-
- - {decoration ?
{decoration}
: null} - {tabFlange ?
: null} -
-
-
- {children} -
+const MobileDisplayControl = (props) => ( + props.expanded + ? +
{}}> +
+
+
+
+ : +
{}}> +
+
+
- ); - } -} - -ChIPSeqTabPanel.propTypes = { - /** Object with tab=>pane specifications */ - tabList: PropTypes.array.isRequired, - /** key of tab to select; it's null for no-selection */ - selectedTab: PropTypes.string, - /** Classes to add to navigation
    */ - navCss: PropTypes.string, - /** Other components to render in the tab bar */ - moreComponents: PropTypes.object, - /** Classes to add to moreComponents wrapper
    */ - moreComponentsClasses: PropTypes.string, - /** True to show a small full-width strip under active tab */ - tabFlange: PropTypes.bool, - /** Component to render in the tab bar */ - decoration: PropTypes.object, - /** CSS classes to wrap decoration in */ - decorationClasses: PropTypes.string, - /** If selectedTab is provided, then parent must keep track of it */ - handleTabClick: PropTypes.func, - children: PropTypes.node, - /** Colors of the fonts */ - fontColors: PropTypes.array, -}; - -ChIPSeqTabPanel.contextTypes = { - location_href: PropTypes.string, - navigate: PropTypes.func, -}; - -ChIPSeqTabPanel.defaultProps = { - fontColors: null, - selectedTab: '', - navCss: null, - moreComponents: null, - moreComponentsClasses: '', - tabFlange: false, - decoration: null, - decorationClasses: null, - handleTabClick: null, - children: null, -}; - - -/** - * Used for creating modal pop up that gathers information on what organism a user wants to view. - * - */ -const SelectOrganismModal = () => ( - - -

    ChIP-Seq Matrix — choose organism

    -
    - -
    Organism to view in matrix:
    -
    - {tabLevel1.map((tab, index) => ( - {tab.header} - ))} +
    +
    +
    - - ); - -/** - * Container for ChIP-Seq Matrix page's content. - * - * @class ChIPSeqMatrixPresentation - * @extends {React.Component} - * @listens PubSub - SEARCH_PERFORMED_PUBSUB - */ -class ChIPSeqMatrixPresentation extends React.Component { - constructor(props) { - super(props); - - this.subTabClicked = this.subTabClicked.bind(this); - this.performSearch = this.performSearch.bind(this); - this.handleOnScroll = this.handleOnScroll.bind(this); - this.handleScrollIndicator = this.handleScrollIndicator.bind(this); +
    +); - const { context } = this.props; - const link = context['@id']; - const query = new QueryString(link); - const assayTitle = assayTitlesOptions.filter((tab) => query.getKeyValues('assay_title').includes(tab))[0]; - const organismName = query.getKeyValues('replicates.library.biosample.donor.organism.scientific_name')[0]; - const selectedTabLevel1 = (tabLevel1.find((tab) => tab.id === organismName) || tabLevel1[0]).id; - const selectedTabLevel2 = (tabLevel2.find((tab) => tab.id === assayTitle) || tabLevel2[0]).id; +MobileDisplayControl.propTypes = { + expanded: PropTypes.bool.isRequired, + setLayout: PropTypes.func.isRequired, +}; - this.subTabs = []; - this.ChIPSeqMatrixData = []; - this.state = { - chIPSeqData: [], - scrolledRight: false, - showOrganismRequest: false, - spinnerActive: true, - organismName, - selectedTabLevel1, - selectedTabLevel2, - }; +const getCardIndice = (data) => { + const closedCard1Start = 0; + let closedCard1End = 0; + let openCardStart = 0; + let openCardEnd = 0; + let closedCard2Start = 0; + let closedCard2End = 0; + const dataCount = data.length; + + if (dataCount <= EXPANDED_CARD_SIZE) { + openCardEnd = EXPANDED_CARD_SIZE; + } else if (dataCount === EXPANDED_CARD_SIZE + 1) { + closedCard1End = 1; + openCardStart = closedCard1End + 1; + openCardEnd = EXPANDED_CARD_SIZE; + } else { + const closedCardCount = dataCount - EXPANDED_CARD_SIZE; + const firstClosedCardRegionCount = Math.floor(closedCardCount / 2); + + closedCard1End = firstClosedCardRegionCount; + openCardStart = closedCard1End; + openCardEnd = closedCard1End + EXPANDED_CARD_SIZE; + closedCard2Start = openCardEnd; + closedCard2End = dataCount; } - componentDidMount() { - this.handleScrollIndicator(this.scrollElement); - - // extract ChIP-Seq Matrix data and get relevant values out - const { context } = this.props; - const link = context['@id']; - const query = new QueryString(link); - const assayTitle = query.getKeyValues('assay_title')[0]; - const organismName = query.getKeyValues('replicates.library.biosample.donor.organism.scientific_name')[0]; - const showOrganismRequest = !(assayTitle && organismName); + return { + closedCard1Start, + closedCard1End, + openCardStart, + openCardEnd, + closedCard2Start, + closedCard2End, + }; +}; - // ALL ChIP-Seq Matrix data - this.ChIPSeqMatrixData = getChIPSeqData(context, assayTitle, organismName); +const Carousel = () => { + const [expanded, setExpanded] = React.useState(true); + const [cards, setCards] = React.useState([...newPageData]); + const { + closedCard1Start, + closedCard1End, + openCardStart, + openCardEnd, + closedCard2Start, + closedCard2End, + } = getCardIndice(cards); + let closedCards1; + let openedCards; + let closedCards2; + + const setLayout = () => { + setExpanded(!expanded); + }; - // sub tabs - this.subTabs = this.ChIPSeqMatrixData ? this.ChIPSeqMatrixData.subTabs : []; + const shift = (direction) => { + const arr = [...cards]; - // subtab may be in the url #, get it if it is there or default to first subtabs list value - const storedSelectedTabLevel3 = window.sessionStorage.getItem('encodeSelectedTabLevel3'); - const selectedTabLevel3 = storedSelectedTabLevel3 && this.subTabs.includes(storedSelectedTabLevel3) ? - storedSelectedTabLevel3 : - this.subTabs.length > 0 ? this.ChIPSeqMatrixData.subTabs[0] : null; + if (direction === 1) { + const firstItem = arr.shift(); + arr.push(firstItem); - // Note: Hacky. If assay title is Histone and the organism is worm or fly, direct the user to TF assay title - if (assayTitle === 'Histone ChIP-seq' && organismsWithHiddenHistoneAssay.includes(organismName)) { - const tfUrl = link.replace('assay_title=Histone%20ChIP-seq', 'assay_title=TF ChIP-seq').replace('assay_title=Histone ChIP-seq', 'assay_title=TF ChIP-seq'); - this.context.navigate(tfUrl); + setCards(arr); + return; } - // sub chIP Seq data to display - const chIPSeqData = this.ChIPSeqMatrixData ? this.ChIPSeqMatrixData.chIPSeqData[selectedTabLevel3] : {}; - const matrixUpdate = { - chIPSeqData, - selectedTabLevel3, - showOrganismRequest, // determines if organism modal shows - }; - - this.setState({ spinnerActive: true }, () => { - // deferred so spinner is painted on screen before matrix is painted - _.defer(() => { - this.setState(matrixUpdate, () => { - this.setState({ spinnerActive: false }); - }); - }); - }); - - this.searchSubcription = PubSub.subscribe(SEARCH_PERFORMED_PUBSUB, this.performSearch); - } - - componentDidUpdate() { - // Updates only happen for scrolling on this page. Every other update causes an - // unmount/mount sequence. - this.handleScrollIndicator(this.scrollElement); - } - - // these are important to reset if code blows up - componentDidCatch() { - this.setState({ showOrganismRequest: false, spinnerActive: false }); - } - - componentWillUnmount() { - PubSub.unsubscribe(this.searchSubcription); - } + const lastItem = arr.pop(); + setCards([lastItem, ...arr]); + }; - /** - * Called when the user scrolls the matrix horizontally within its div to handle scroll - * indicators - * @param {object} e React synthetic scroll event - */ - handleOnScroll(e) { - this.handleScrollIndicator(e.target); + if (closedCard1End !== 0) { + closedCards1 = cards.slice(closedCard1Start, closedCard1End).map((item) => ); } - /** - * Show a scroll indicator depending on current scrolled position. - * @param {object} element DOM element to apply shading to - */ - handleScrollIndicator(element) { - if (element) { - // Have to use a "roughly equal to" test because of an MS Edge bug mentioned here: - // https://stackoverflow.com/questions/30900154/workaround-for-issue-with-ie-scrollwidth - const scrollDiff = Math.abs((element.scrollWidth - element.scrollLeft) - element.clientWidth); - if (scrollDiff < 2 && !this.state.scrolledRight) { - // Right edge of matrix scrolled into view. - this.setState({ scrolledRight: true }); - } else if (scrollDiff >= 2 && this.state.scrolledRight) { - // Right edge of matrix scrolled out of view. - this.setState({ scrolledRight: false }); - } - } else if (!this.state.scrolledRight) { - this.setState({ scrolledRight: true }); - } + if (openCardEnd !== 0) { + openedCards = cards.slice(openCardStart, openCardEnd).map((item) => ); } - /** - * A subtab is clicked. - * - * Its mains job is to extract required data from chIPSeqData object. This is computationally cheaper than - * refetching a new context and going off that. - * - * @param {object} e - event object - * @memberof ChIPSeqMatrixPresentation - */ - subTabClicked(e) { - PubSub.publish(CLEAR_SEARCH_BOX_PUBSUB, {}); - const index = Number(e.target.dataset.key); - const selectedTabLevel3 = this.subTabs[Number.isNaN(index) ? 0 : index]; - window.sessionStorage.setItem('encodeSelectedTabLevel3', selectedTabLevel3); - const chIPSeqData = this.ChIPSeqMatrixData.chIPSeqData[selectedTabLevel3]; - - this.setState({ spinnerActive: true }, () => { - // deferred so spinner is painted on screen before matrix is painted - _.defer(() => { - this.setState({ chIPSeqData: null }, () => { // chIPSeqData set to null to prevent react from doing a diff - this.setState({ selectedTabLevel3, chIPSeqData }, () => { - this.setState({ spinnerActive: false }); - }); - }); - }); - }); + if (closedCard2Start !== 0) { + closedCards2 = cards.slice(closedCard2Start, closedCard2End).map((item) => ); } - /** - * User is searching - * - * PubSub used because it is easier to let anyone clear search box and/or get search data - * - * @param {string} message - Message from PubSub - * @param {object} searchData - Information on what search user is doing - * @memberof ChIPSeqMatrixPresentation - */ - performSearch(message, searchData) { - const searchText = searchData.text.toLocaleLowerCase().trim(); - const chIPSeqData = { ...this.ChIPSeqMatrixData.chIPSeqData[this.state.selectedTabLevel3] }; - let dataRow = []; - let headerRow = []; - - if (searchText) { - const searchField = searchData.option === 'biosample' ? 'headerRow' : 'dataRow'; - const selectedAxis = chIPSeqData[searchField] || []; - - // searching biosample - if (searchField === 'headerRow') { - const filterResultIndexes = selectedAxis.map((m, i) => { - if (m.toLocaleLowerCase().indexOf(searchText) !== -1) { - return i; - } - return null; - }).filter((m) => m !== null); - - const dataRowLength = chIPSeqData.dataRow.length; - - // .fill([]) duplicate the same array reference rather than create a new array - // so map was used - dataRow = [...Array(dataRowLength)].map(() => []); - - headerRow = []; - - filterResultIndexes.forEach((i) => { - headerRow.push(chIPSeqData.headerRow[i]); - }); - - for (let j = 0; j < dataRowLength; j += 1) { - // get text of first entry - dataRow[j].push(chIPSeqData.dataRow[j][0]); - - // get entries other than the first - for (let k = 0; k < filterResultIndexes.length; k += 1) { - // header row is offset by 1 compared to data row - dataRow[j].push(chIPSeqData.dataRow[j][filterResultIndexes[k] + 1]); - } - } - } else { // searching target - dataRow = selectedAxis.map((y) => (y[0].trim().toLocaleLowerCase().indexOf(searchText) !== -1 ? y : null)).filter((f) => f !== null); - ({ headerRow } = chIPSeqData); - } - - // clear data if both data or header row if either is empty, so show no-data message - if (headerRow.length === 0) { - dataRow = []; - } else if (dataRow.length === 0) { - headerRow = []; - } - - chIPSeqData.headerRow = headerRow; - chIPSeqData.dataRow = dataRow; - } - - this.setState({ spinnerActive: true }, () => { - // deferred so spinner is painted on screen before matrix is painted - _.defer(() => { - this.setState({ chIPSeqData: null }, () => { - this.setState({ chIPSeqData }, () => { - this.setState({ spinnerActive: false }); - }); - }); - }); - }); - } - - render() { - const { context } = this.props; - const { scrolledRight, chIPSeqData, showOrganismRequest, selectedTabLevel1, selectedTabLevel2, selectedTabLevel3, spinnerActive, organismName } = this.state; - const subTabsHeaders = this.subTabs.map((subTab, index) => ({ // subtabs formatted to for displaying - id: (subTab || index.toString()).trim(' '), - header: subTab, - })); - - // NOTE: In fly (Drosophila melanogaster) and worm (Caenorhabditis elegans), Histone ChIP-seq are hidden - const hideAssayTitle = selectedTabLevel1 && organismsWithHiddenHistoneAssay.includes(selectedTabLevel1); - const filteredTabLevel2 = hideAssayTitle ? - tabLevel2.filter((tab) => tab.id !== 'Histone ChIP-seq') : - tabLevel2; - - for (let i = 0; i < tabLevel1.length; i += 1) { - const tab1 = tabLevel1[i]; - tab1.url = `replicates.library.biosample.donor.organism.scientific_name=${tab1.id}&assay_title=${selectedTabLevel2}${selectedTabLevel2 === 'Histone ChIP-seq' ? '&assay_title=Mint-ChIP-seq' : ''}`; - } - - for (let i = 0; i < tabLevel2.length; i += 1) { - const assay = tabLevel2[i].id; - tabLevel2[i].url = `replicates.library.biosample.donor.organism.scientific_name=${organismName}&assay_title=${assay}${assay === 'Histone ChIP-seq' ? '&assay_title=Mint-ChIP-seq' : ''}`; - } - - return ( -
    - -
    - biosample - {svgIcon('largeArrow')} + return ( + <> +
    +
    +
    -
    -
    {svgIcon('largeArrow')}{context.matrix.y.label}
    - {showOrganismRequest ? : null } - - - - {chIPSeqData && chIPSeqData.headerRow && chIPSeqData.headerRow.length !== 0 && chIPSeqData.dataRow && chIPSeqData.dataRow.length !== 0 ? -
    { this.scrollElement = element; }}> - -
    - : -
    - { chIPSeqData && Object.keys(chIPSeqData).length === 0 ? 'Select an organism to view data.' : 'No data to display.' } -
    - } -
    -
    -
    +
    +
    + { + cards.map((item) => ) + } +
    -
    ); - } -} - -ChIPSeqMatrixPresentation.propTypes = { - context: PropTypes.object.isRequired, -}; - -ChIPSeqMatrixPresentation.contextTypes = { - navigate: PropTypes.func, - location_href: PropTypes.string, - session: PropTypes.object, - session_properties: PropTypes.object, +
    +
    +
    shift(1)} role="button" tabIndex={-1} onKeyPress={() => {}}> + +
    +
    +
    + { closedCards1 } + { openedCards } + { closedCards2 } +
    +
    +
    shift(-1)} role="button" tabIndex={-1} onKeyPress={() => {}}> + +
    +
    + + ); }; - /** * Container for ChIP-Seq Matrix page. * @@ -922,80 +294,71 @@ ChIPSeqMatrixPresentation.contextTypes = { * @returns */ const ChIPSeqMatrix = ({ context }) => { - const itemClass = globals.itemClass(context, 'view-item'); - - return ( - - - - - - - ); -}; - -ChIPSeqMatrix.propTypes = { - context: PropTypes.object.isRequired, -}; - -ChIPSeqMatrix.contextTypes = { - location_href: PropTypes.string, - navigate: PropTypes.func, - biosampleTypeColors: PropTypes.object, // DataColor instance for experiment project -}; - -/** - * Render the vertical facets. - */ -const ChIPSeqMatrixFacets = ({ context }) => { - const [facetOpen, setFacetOpen] = React.useState(false); - - useMount(() => { - const facetOpenSession = window.sessionStorage.getItem(`${FACET_SESSION_SUFFIX}_facetOpen`); - - if (facetOpenSession) { - setFacetOpen(facetOpenSession === 'true'); - } - }); + const i = 0; + console.log(i); + const clear = React.useRef(null); + const form = React.useRef(null); + + const resetSearch = () => { + form.current.reset(); + clear.current.style.display = 'none'; + }; - const facetClicked = () => { - const isFacetOpen = !facetOpen; + const searchBoxKeyUp = (e) => { + const { target } = e; - setFacetOpen(isFacetOpen); - window.sessionStorage.setItem(`${FACET_SESSION_SUFFIX}_facetOpen`, isFacetOpen.toString()); + clear.current.style.display = target.value.trim() === '' ? 'none' : 'inline'; + console.error(target.value); }; return ( -
    -
    -
    - -
    -
    - +
    +
    + text +
    +
    +
    +
    +
    + Encode search +
    +
    Search ENCODE portal
    + } + tooltipId="search-encode" + css="tooltip-home-info" + > + Search the entire ENCODE portal by using terms like “skin,” “ChIP-seq,” or “CTCF.” + +
    +
    + searchBoxKeyUp(e)} /> + +
    + resetSearch()} /> +
    +
    -
    - { facetOpen ? - displayedFacets.includes(facet.field))} - filters={context.filters} - addClasses="matrix-facets" - supressTitle - /> - : null - } +
    +
    + +
    ); }; -ChIPSeqMatrixFacets.propTypes = { - /** Matrix search result object */ +ChIPSeqMatrix.propTypes = { context: PropTypes.object.isRequired, }; +ChIPSeqMatrix.contextTypes = { + location_href: PropTypes.string, + navigate: PropTypes.func, + biosampleTypeColors: PropTypes.object, // DataColor instance for experiment project +}; + globals.contentViews.register(ChIPSeqMatrix, 'ChipSeqMatrix'); diff --git a/src/encoded/static/components/new_home_page.js b/src/encoded/static/components/new_home_page.js new file mode 100644 index 00000000000..0f70b4f1f3d --- /dev/null +++ b/src/encoded/static/components/new_home_page.js @@ -0,0 +1,12 @@ +import * as globals from './globals'; + + +const NewHomePage = () => { + const i = 'test'; + + return ( +
    {i}
    + ); +}; + +globals.contentViews.register(NewHomePage, 'new-home-page'); diff --git a/src/encoded/static/scss/encoded/modules/_new_home_page.scss b/src/encoded/static/scss/encoded/modules/_new_home_page.scss new file mode 100644 index 00000000000..ac5f29ce95a --- /dev/null +++ b/src/encoded/static/scss/encoded/modules/_new_home_page.scss @@ -0,0 +1,267 @@ +$box-color: #0a253d; +$search-radius: 10px; + +.home-page-layout { + display: flex; + flex-direction: column; + + &__brand { + flex: 0 1 20%; + display: flex; + justify-content: center; + margin-bottom: 20px; + + &--brand-img { + height: 65%; + width: 70%; + } + } + + &__search { + flex: 0 1 10%; + display: flex; + justify-content: center; + margin-bottom: 100px; + + &__search-region { + height: 20px; + width: 100%; + + ul { + list-style: none; + + input[type="text"] { + border-radius: 25px; + width: 100%; + border: 1px solid #ccc; + } + } + + p { + margin: 0; + text-align: center; + } + + @media screen and (min-width: $screen-md-min) { + width: 60%; + } + + &__text-region { + display: flex; + justify-content: center; + } + + .tooltip-home-info { + margin-top: 7px; + } + + &__search { + display: flex; + border: 1px solid gray; + border-radius: $search-radius; + + input[type="search"] { + width: 100%; + border: 0 solid white; + border-radius: $search-radius; + } + + button { + background-color: white; + border: 0 solid white; + border-radius: 10px; + } + } + + &__clear { + display: none; + float: right; + margin-top: 10px; + background-color: #bc0000; + border-radius: $search-radius; + font-size: 0.9rem; + } + } + } + + &__caroursel { + flex: 0 1 70%; + } +} + +.home-page-layout-control { + &__expanded { + display: flex; + justify-content: space-between; + width: 60px; + float: right; + margin-right: 10px; + cursor: pointer; + + &__first-item { + background-color: $box-color; + width: 10px; + height: 15px; + } + + &__second-item { + background-color: $box-color; + width: 30px; + height: 15px; + } + + &__third-item { + background-color: $box-color; + width: 10px; + height: 15px; + } + } + + &__collapsed { + display: flex; + justify-content: space-between; + width: 10px; + float: right; + margin-right: 10px; + cursor: pointer; + + &__item { + background-color: $box-color; + height: 5px; + margin: 2px; + width: 5px; + } + } +} + +.home-page-full-view { + display: none; + justify-content: space-between; + + &__arrow { + flex: 0 1 2%; + display: flex; + align-items: center; + justify-content: center; + max-width: 50%; + cursor: pointer; + + i { + font-size: 30px; + } + } + + &__region { + flex: 0 1 96%; + overflow: hidden; + + &__carousel { + display: flex; + } + } +} + +.home-page-mobile-view { + display: flex; + flex-direction: column; +} + +.home-page-mobile-card-view { + // clear: both; +} + +.home-page-cards-mobile-expanded { + display: flex; + justify-content: center; + flex-wrap: wrap; +} + +.home-page-cards-mobile-collapsed { + display: flex; + overflow: scroll; +} + +$card-height: 330px; +$card-width: 180px; +$card-margin: 5px; + +@mixin ellipsis { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.home-page-card { + display: flex; + border: 1px solid black; + flex-direction: column; + margin: $card-margin; + height: $card-height; + width: $card-width; + + &__header { + color: white; + background-color: $box-color; + flex: 0 1 10%; + text-align: center; + padding: 5px; + height: $card-height / (0.1); + width: $card-width; + + @include ellipsis(); + } + + &__content { + color: gray; + background-color: white; + flex: 0 1 50%; + text-align: center; + height: $card-height / (0.5); + padding: 5px; + + @include ellipsis(); + } + + &__footer { + color: white; + background-color: $box-color; + flex: 0 1 40%; + padding: 5px; + + @include ellipsis(); + } +} + +.home-page-closed-card { + display: flex; + border: 1px solid black; + flex-direction: column; + margin: $card-margin; + height: $card-height; + width: 40px; + + &__content { + flex: 0 1 100%; + color: white; + background-color: $box-color; + + @include ellipsis(); + + &--text { + text-align: center; + transform: rotate(-90deg); + position: relative; + top: 50%; + } + } +} + +@media screen and (min-width: $screen-md-min) { + .home-page-mobile-view { + display: none; + } + + .home-page-full-view { + display: flex; + } +} diff --git a/src/encoded/static/scss/style.scss b/src/encoded/static/scss/style.scss index d6ce4d60082..57711d1ab6c 100644 --- a/src/encoded/static/scss/style.scss +++ b/src/encoded/static/scss/style.scss @@ -227,6 +227,7 @@ $link-hover-color: darken($link-color, 15%); "encoded/modules/lightbox", "encoded/modules/facet", "encoded/modules/matrix", + "encoded/modules/new_home_page", "encoded/modules/auditmatrix", "encoded/modules/collection", "encoded/modules/lists",