diff --git a/src/client/features/search/pathway-results-view.js b/src/client/features/search/pathway-results-view.js index a35d65a68..84670c88a 100644 --- a/src/client/features/search/pathway-results-view.js +++ b/src/client/features/search/pathway-results-view.js @@ -3,6 +3,7 @@ const h = require('react-hyperscript'); const Link = require('react-router-dom').Link; const queryString = require('query-string'); const _ = require('lodash'); +const classNames = require('classnames'); const { ErrorMessage } = require('../../common/components/error-message'); @@ -19,21 +20,43 @@ class PathwayResultsView extends React.Component { return null; } - const searchList = pathwayResults.map(result => { + const searchList = pathwayResults.map( ( result, index ) => { let dsInfo = _.get( result, 'sourceInfo', '' ); let iconUrl = dsInfo.iconUrl || ''; let name = dsInfo.name || ''; - - return h('div.search-item', [ - h('div.search-item-icon',[ - h('img', {src: iconUrl}) + const pathwayTitle = result.name; + const topHit = index === 0; + const hasPreview = result.previewUrl; + const showPreview = topHit && hasPreview; + + let item; + const itemLink = children => h(Link, { className: 'plain-link', to: { pathname: '/pathways', search: queryString.stringify({ uri: result.uri }) }, target: '_blank' }, children || 'N/A'); + const itemPreview = h('img.search-item-preview', {src: result.previewUrl}); + const itemInfo = title => h('div.search-item-info', [ + h('div.search-item-icon', [ + h('img', {src: iconUrl}) ]), h('div.search-item-content', [ - h(Link, { className: 'plain-link', to: { pathname: '/pathways', search: queryString.stringify({ uri: result.uri }) }, target: '_blank' }, [result.name || 'N/A']), + title, h('p.search-item-content-datasource', ` ${name}`), h('p.search-item-content-participants', `${result.numParticipants} Participants`) ]) ]); + + if( showPreview ){ + // Wrap the entire item in a link + item = itemLink([ + itemInfo( pathwayTitle ), + itemPreview + ]); + } else { + // Associate the link with the content + item = itemInfo( itemLink( pathwayTitle ) ); + } + + return h('div.search-item', { + className: classNames({ 'preview': showPreview }) + }, item ); }); const searchResultFilter = h('div.search-filters', [ diff --git a/src/scripts/cli.js b/src/scripts/cli.js index 62a214f20..950f4a486 100644 --- a/src/scripts/cli.js +++ b/src/scripts/cli.js @@ -9,6 +9,7 @@ const fsPromises = require('fs').promises; const readline = require( 'readline' ); const retry = require( 'async-retry' ); +const { uri2filename } = require( '../util/uri.js' ); const logger = require( '../server/logger.js' ); const { DOWNLOADS_FOLDER_NAME, @@ -169,9 +170,6 @@ const parsePCGmtLine = line => { return { uri, meta, genes }; }; -// Create file safe names from a uri -const uri2filename = s => s.replace(/[^a-z0-9]/gi, '_').toLowerCase(); - async function getStore( ) { const store = { async save( item ){ diff --git a/src/server/external-services/pathway-commons.js b/src/server/external-services/pathway-commons.js index 129451ac5..13ad190de 100644 --- a/src/server/external-services/pathway-commons.js +++ b/src/server/external-services/pathway-commons.js @@ -1,3 +1,5 @@ +const fs = require('fs'); +const path = require('path'); const qs = require('query-string'); const url = require('url'); const QuickLRU = require('quick-lru'); @@ -8,6 +10,7 @@ const { cachePromise } = require('../cache'); const { fetch } = require('../../util'); const logger = require('../logger'); const config = require('../../config'); +const { uri2filename } = require('../../util/uri.js'); const xrefCache = new QuickLRU({ maxSize: config.PC_CACHE_MAX_SIZE }); const queryCache = new QuickLRU({ maxSize: config.PC_CACHE_MAX_SIZE }); @@ -105,7 +108,21 @@ const addSourceInfo = async function( searchHit, dataSources ) { return searchHit; }; +// Fill in preview URL +const addPreviewUrl = function( searchHit ) { + const { uri } = searchHit; + const fname = uri2filename( uri ); + const fpath = path.resolve( config.SBGN_IMG_PATH, `${fname}.png` ); + const hasImage = fs.existsSync( fpath ); + if ( hasImage ){ + const previewUrl = fpath.split( path.sep ).slice(-3).join(path.sep); + searchHit.previewUrl = previewUrl; + } + return searchHit; +}; + const augmentSearchHits = async function( searchHits ) { + if( searchHits.length ) addPreviewUrl( searchHits[0] ); const dataSources = await getDataSourcesMap(); return Promise.all( searchHits.map( searchHit => addSourceInfo( searchHit, dataSources ) ) ); }; diff --git a/src/styles/features/search.css b/src/styles/features/search.css index c1d2a56dd..6db7ef31a 100644 --- a/src/styles/features/search.css +++ b/src/styles/features/search.css @@ -201,10 +201,18 @@ } .search-item { + padding: 1em 0; +} + +.search-item.preview { + /* background-color: var(--cloud); */ + border: 1px solid var(--cloud-darker); +} + +.search-item-info { display: flex; vertical-align: middle; align-items: center; - padding: 1em 0; } .search-item-icon { @@ -238,6 +246,13 @@ font-size: 0.66em; } +.search-item-preview { + display: block; + margin-left: auto; + margin-right: auto; + width: 70%; +} + .loader { color: var(--accent-1-colour); } diff --git a/src/util/uri.js b/src/util/uri.js new file mode 100644 index 000000000..2373f1b7e --- /dev/null +++ b/src/util/uri.js @@ -0,0 +1,6 @@ +// Create file safe names from a uri +const uri2filename = s => s.replace(/[^a-z0-9]/gi, '_').toLowerCase(); + +module.exports = { + uri2filename +}; \ No newline at end of file