diff --git a/CHANGELOG.md b/CHANGELOG.md index 1abbb9e9ca2..c3d53cac783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md). ### Changed +- The Dataset Gallery was redesigned to be a Publication Gallery instead. It will feature scientific publications together with their published datasets and information such as the species, brain region or acquisition method of such datasets. [#3653](https://github.com/scalableminds/webknossos/pull/3653) - Annotations for non-public datasets can now be shared using the "Share" functionality without making the dataset public. [#3664](https://github.com/scalableminds/webknossos/pull/3664) - Statistics are now separated by organization, rather than showing the webKnossos instance’s totals. [#3663](https://github.com/scalableminds/webknossos/pull/3663) diff --git a/MIGRATIONS.md b/MIGRATIONS.md index 8eb2bcc317c..aa4cb54d8bf 100644 --- a/MIGRATIONS.md +++ b/MIGRATIONS.md @@ -5,7 +5,13 @@ This project adheres to [Calendar Versioning](http://calver.org/) `0Y.0M.MICRO`. User-facing changes are documented in the [changelog](CHANGELOG.md). ## Unreleased -- +- WebKnossos has a publication gallery now. There is no public interface to create publications yet, but instead those need to be inserted into the database directly. + Publications and additional dataset properties that are displayed in the gallery as well, can be inserted as follows: + ``` + insert into webknossos.publications(_id, publicationDate, imageUrl, title, description) values('5c3c9ec895010095014759fd', NOW(), '', '', '<DESCRIPTION>'); + + update webknossos.datasets set _publication = '5c3c9ec895010095014759fd', details='{"species":"<e.g. Mouse>", "brain-region":"<e.g. cortex>", "acquisition":"<e.g. Raw CLSM data>"}' where _id = '<DATASET_ID>' ; + ``` ### Postgres Evolutions: - [037-add-publications.sql](conf/evolutions/037-add-publications.sql) diff --git a/app/assets/javascripts/admin/api_flow_types.js b/app/assets/javascripts/admin/api_flow_types.js index 43ec071b3b8..b6a22381f22 100644 --- a/app/assets/javascripts/admin/api_flow_types.js +++ b/app/assets/javascripts/admin/api_flow_types.js @@ -78,16 +78,32 @@ export type APITeam = { +organization: string, }; +type APIPublication = { + +created: number, + +description: string, + +id: string, + +imageUrl: string, + +publicationDate: number, + +title: string, +}; + export type APIDatasetId = { +owningOrganization: string, +name: string, }; +export type APIDatasetDetails = { + +species?: string, + +brainRegion?: string, + +acquisition?: string, +}; + type APIDatasetBase = APIDatasetId & { +allowedTeams: Array<APITeam>, +created: number, +dataStore: APIDataStore, +description: ?string, + +details: ?APIDatasetDetails, +isEditable: boolean, +isPublic: boolean, +displayName: ?string, @@ -95,6 +111,7 @@ type APIDatasetBase = APIDatasetId & { +lastUsedByUser: number, +isForeign: boolean, +sortingKey: number, + +publication: ?APIPublication, }; export type APIMaybeUnimportedDataset = APIDatasetBase & { diff --git a/app/assets/javascripts/dashboard/advanced_dataset/dataset_action_view.js b/app/assets/javascripts/dashboard/advanced_dataset/dataset_action_view.js index 98fc1faa8df..77f58e4aa95 100644 --- a/app/assets/javascripts/dashboard/advanced_dataset/dataset_action_view.js +++ b/app/assets/javascripts/dashboard/advanced_dataset/dataset_action_view.js @@ -96,12 +96,12 @@ class DatasetActionView extends React.PureComponent<Props, State> { ) : null} </React.Fragment> ) : null} - <a - href={`/datasets/${dataset.owningOrganization}/${dataset.name}/view`} + <Link + to={`/datasets/${dataset.owningOrganization}/${dataset.name}/view`} title="View Dataset" > <Icon type="eye-o" />View - </a> + </Link> {!dataset.isForeign ? ( <React.Fragment> <a diff --git a/app/assets/javascripts/dashboard/dashboard_view.js b/app/assets/javascripts/dashboard/dashboard_view.js index 2c746f9e070..6b334a9e0a7 100644 --- a/app/assets/javascripts/dashboard/dashboard_view.js +++ b/app/assets/javascripts/dashboard/dashboard_view.js @@ -21,7 +21,7 @@ import Request from "libs/request"; const TabPane = Tabs.TabPane; -const validTabKeys = ["datasets", "advanced-datasets", "tasks", "explorativeAnnotations"]; +const validTabKeys = ["publications", "advanced-datasets", "tasks", "explorativeAnnotations"]; type OwnProps = { userId: ?string, @@ -57,7 +57,7 @@ export const datasetCache = { }; export const urlTokenToTabKeyMap = { - gallery: "datasets", + gallery: "publications", datasets: "advanced-datasets", tasks: "tasks", annotations: "explorativeAnnotations", @@ -69,7 +69,7 @@ class DashboardView extends React.PureComponent<Props, State> { const lastUsedTabKey = localStorage.getItem("lastUsedDashboardTab"); const isValid = lastUsedTabKey && validTabKeys.indexOf(lastUsedTabKey) > -1; - const defaultTab = this.props.isAdminView ? "tasks" : "datasets"; + const defaultTab = this.props.isAdminView ? "tasks" : "publications"; const cachedDatasets = datasetCache.get(); @@ -151,7 +151,7 @@ class DashboardView extends React.PureComponent<Props, State> { getTabs(user: APIUser) { if (this.props.activeUser) { - const isAdminView = this.props.isAdminView; + const { isAdminView } = this.props; const datasetViewProps = { user, @@ -162,7 +162,7 @@ class DashboardView extends React.PureComponent<Props, State> { return [ !isAdminView ? ( - <TabPane tab="Dataset Gallery" key="datasets"> + <TabPane tab="Publications" key="publications"> <DatasetView {...datasetViewProps} dataViewType="gallery" /> </TabPane> ) : null, diff --git a/app/assets/javascripts/dashboard/dataset_panel.js b/app/assets/javascripts/dashboard/dataset_panel.js deleted file mode 100644 index 0d808ddabb1..00000000000 --- a/app/assets/javascripts/dashboard/dataset_panel.js +++ /dev/null @@ -1,216 +0,0 @@ -// @flow -import { Row, Col, Card, Popover } from "antd"; -import Markdown from "react-remarkable"; -import * as React from "react"; -import _ from "lodash"; - -import type { APIDataset } from "admin/api_flow_types"; -import { formatScale } from "libs/format_utils"; -import { - getThumbnailURL, - hasSegmentation, - getSegmentationThumbnailURL, -} from "oxalis/model/accessors/dataset_accessor"; - -const columnSpan = { xs: 24, sm: 24, md: 24, lg: 12, xl: 12, xxl: 8 }; -const thumbnailDimension = 500; - -type Props = { - datasets: Array<APIDataset>, - organizationName: string, - showOrganizationHeader: boolean, - croppedDatasetCount: ?number, -}; - -type State = { - showLessContent: boolean, -}; - -function getDisplayName(dataset: APIDataset): string { - return dataset.displayName != null && dataset.displayName !== "" - ? dataset.displayName - : dataset.name; -} - -function getDescription(dataset: APIDataset) { - let freeTextDescription = null; - if (dataset.description) { - freeTextDescription = ( - <Markdown - source={dataset.description} - options={{ html: false, breaks: true, linkify: true }} - /> - ); - } else { - freeTextDescription = hasSegmentation(dataset) ? ( - <p>Original data and segmentation</p> - ) : ( - <p>Original data</p> - ); - } - - return ( - <div> - <p>Scale: {formatScale(dataset.dataSource.scale)}</p> - {freeTextDescription} - </div> - ); -} - -function ThumbnailAndDescription({ - thumbnailURL, - description, - name, - segmentationThumbnailURL, -}: { - thumbnailURL: string, - name: string, - description: React.Element<*> | string, - segmentationThumbnailURL: ?string, -}) { - return ( - <React.Fragment> - <span className="dataset-thumbnail" title="Click to view dataset"> - <div - className="dataset-thumbnail-image" - style={{ - background: `url('${thumbnailURL}?w=${thumbnailDimension}&h=${thumbnailDimension}')`, - backgroundSize: "cover", - width: "100%", - height: "100%", - }} - /> - {segmentationThumbnailURL ? ( - <div - className="dataset-thumbnail-image segmentation" - style={{ - background: `url('${segmentationThumbnailURL}?w=${thumbnailDimension}&h=${thumbnailDimension}')`, - backgroundSize: "cover", - width: "100%", - height: "100%", - position: "absolute", - left: "0", - top: "0", - }} - /> - ) : null} - </span> - <div className="dataset-description"> - <div className="description-flex"> - <h3>{name}</h3> - <div className="dataset-description-body">{description}</div> - </div> - </div> - </React.Fragment> - ); -} - -function ThumbnailAndDescriptionFromDataset({ dataset }: { dataset: APIDataset }) { - return ( - <ThumbnailAndDescription - thumbnailURL={getThumbnailURL(dataset)} - name={getDisplayName(dataset)} - description={getDescription(dataset)} - segmentationThumbnailURL={ - hasSegmentation(dataset) ? getSegmentationThumbnailURL(dataset) : null - } - /> - ); -} - -class DatasetPanel extends React.PureComponent<Props, State> { - constructor(props: Props) { - super(props); - this.state = { - showLessContent: true, - }; - } - - handleClick = () => { - this.setState(prevState => ({ showLessContent: !prevState.showLessContent })); - }; - - renderCard = (dataset: APIDataset) => ( - <a href={`/datasets/${dataset.owningOrganization}/${dataset.name}/view`} title="View Dataset"> - <Card bodyStyle={{ padding: 0 }} className="spotlight-item-card"> - <ThumbnailAndDescriptionFromDataset dataset={dataset} /> - </Card> - </a> - ); - - renderMultiDatasetCard = (groupName: string, datasets: APIDataset[]) => { - const multiDescription = ( - <div> - This collection consists of multiple datasets: - <ul> - {datasets.map(dataset => { - const popoverContent = ( - <div className="spotlight-popover-card"> - <ThumbnailAndDescriptionFromDataset dataset={dataset} /> - </div> - ); - return ( - <li key={dataset.name}> - <Popover - placement="left" - content={popoverContent} - trigger="hover" - overlayClassName="antd-spotlight-popover-card" - > - <a href="#">{getDisplayName(dataset)}</a> - </Popover> - </li> - ); - })} - </ul> - </div> - ); - - return ( - <Card bodyStyle={{ padding: 0 }} className="spotlight-item-card"> - <ThumbnailAndDescription - thumbnailURL={getThumbnailURL(datasets[0])} - name={groupName} - description={multiDescription} - segmentationThumbnailURL={ - hasSegmentation(datasets[0]) ? getSegmentationThumbnailURL(datasets[0]) : null - } - /> - </Card> - ); - }; - - render() { - const groupedDatasets = _.entries( - // Instead of dataset.name, this could be grouped by a group tag - _.groupBy(this.props.datasets, dataset => dataset.name), - ); - const maybeCroppedDatasetsGroup = - this.state.showLessContent && this.props.croppedDatasetCount != null - ? groupedDatasets.slice(0, this.props.croppedDatasetCount) - : groupedDatasets; - - return ( - <div className="dataset-panel"> - {this.props.showOrganizationHeader && <h1>{this.props.organizationName}</h1>} - <Row gutter={16}> - {maybeCroppedDatasetsGroup.map(([groupName, datasets]) => ( - <Col className="gallery-dataset-col" {...columnSpan} key={groupName}> - {datasets.length === 1 - ? this.renderCard(datasets[0]) - : this.renderMultiDatasetCard(groupName, datasets)} - </Col> - ))} - </Row> - {this.props.croppedDatasetCount != null && - groupedDatasets.length > this.props.croppedDatasetCount ? ( - <a className="show-more-link" onClick={this.handleClick}> - {this.state.showLessContent ? "show more" : "show less"} - </a> - ) : null} - </div> - ); - } -} - -export default DatasetPanel; diff --git a/app/assets/javascripts/dashboard/dataset_view.js b/app/assets/javascripts/dashboard/dataset_view.js index 0e79b999cb2..bccafe24633 100644 --- a/app/assets/javascripts/dashboard/dataset_view.js +++ b/app/assets/javascripts/dashboard/dataset_view.js @@ -7,7 +7,7 @@ import * as React from "react"; import type { APIUser, APIMaybeUnimportedDataset } from "admin/api_flow_types"; import AdvancedDatasetView from "dashboard/advanced_dataset/advanced_dataset_view"; -import GalleryDatasetView from "dashboard/gallery_dataset_view"; +import PublicationView from "dashboard/publication_view"; import Persistence from "libs/persistence"; import * as Utils from "libs/utils"; @@ -86,9 +86,7 @@ class DatasetView extends React.PureComponent<Props, State> { } renderGallery() { - return ( - <GalleryDatasetView datasets={this.props.datasets} searchQuery={this.state.searchQuery} /> - ); + return <PublicationView datasets={this.props.datasets} searchQuery={this.state.searchQuery} />; } renderAdvanced() { @@ -144,7 +142,7 @@ class DatasetView extends React.PureComponent<Props, State> { return ( <div> {adminHeader} - <h3 className="TestDatasetHeadline">Datasets</h3> + <h3 className="TestDatasetHeadline">{isGallery ? "Publications" : "Datasets"}</h3> <div className="clearfix" style={{ margin: "20px 0px" }} /> <Spin size="large" spinning={this.props.datasets.length === 0 && this.props.isLoading}> {content} diff --git a/app/assets/javascripts/dashboard/gallery_dataset_view.js b/app/assets/javascripts/dashboard/gallery_dataset_view.js deleted file mode 100644 index b47a85b67d9..00000000000 --- a/app/assets/javascripts/dashboard/gallery_dataset_view.js +++ /dev/null @@ -1,85 +0,0 @@ -// @flow -import * as React from "react"; -import _ from "lodash"; - -import type { APIDataset, APIMaybeUnimportedDataset } from "admin/api_flow_types"; -import { getOrganizations } from "admin/admin_rest_api"; -import DatasetPanel from "dashboard/dataset_panel"; -import * as Utils from "libs/utils"; - -type State = { - organizationNameMap: { [key: string]: string }, -}; - -type Props = { - datasets: Array<APIMaybeUnimportedDataset>, - searchQuery: string, -}; - -const croppedDatasetCount = 6; - -class GalleryDatasetView extends React.PureComponent<Props, State> { - state = { - organizationNameMap: {}, - }; - - componentDidMount() { - this.fetch(); - } - - async fetch() { - const organizations = await getOrganizations(); - - this.setState({ - organizationNameMap: _.mapValues(_.keyBy(organizations, "name"), org => org.displayName), - }); - } - - render() { - // $FlowFixMe flow doesn't check that after filtering there are only imported datasets left - const activeDatasets: Array<APIDataset> = this.props.datasets.filter(ds => ds.isActive); - const filteredDatasets = Utils.filterWithSearchQueryAND( - activeDatasets, - ["name", "description"], - this.props.searchQuery, - ); - - const groupedDatasets = _.chain(filteredDatasets) - .groupBy("owningOrganization") - .entries() - .map(([organization, datasets]) => - // Sort each group of datasets - [ - organization, - datasets.sort(Utils.compareBy(([]: APIDataset[]), dataset => dataset.sortingKey, false)), - ], - ) - .value() - .sort( - // Sort groups by creation date of first dataset - Utils.compareBy( - ([]: Array<[string, Array<APIDataset>]>), - ([_organization, datasets]) => datasets[0].sortingKey, - false, - ), - ); - - const hasMultipleOrganizations = groupedDatasets.length > 1; - return ( - <React.Fragment> - {groupedDatasets.map(([organization, datasets]) => ( - <DatasetPanel - showOrganizationHeader={hasMultipleOrganizations} - croppedDatasetCount={croppedDatasetCount} - className="dataset-panel" - key={organization} - organizationName={this.state.organizationNameMap[organization] || organization} - datasets={datasets} - /> - ))} - </React.Fragment> - ); - } -} - -export default GalleryDatasetView; diff --git a/app/assets/javascripts/dashboard/publication_card.js b/app/assets/javascripts/dashboard/publication_card.js new file mode 100644 index 00000000000..309efbd0b52 --- /dev/null +++ b/app/assets/javascripts/dashboard/publication_card.js @@ -0,0 +1,208 @@ +// @flow +import { Card, Button } from "antd"; +import Markdown from "react-remarkable"; +import * as React from "react"; +import classNames from "classnames"; +import { Link } from "react-router-dom"; + +import type { APIDataset, APIDatasetId, APIDatasetDetails } from "admin/api_flow_types"; +import { formatScale } from "libs/format_utils"; +import { + getThumbnailURL, + hasSegmentation, + getSegmentationThumbnailURL, +} from "oxalis/model/accessors/dataset_accessor"; + +type ExtendedDatasetDetails = { ...APIDatasetDetails, name: string, scale: string }; + +const thumbnailDimension = 500; +const miniThumbnailDimension = 75; + +function getDisplayName(dataset: APIDataset): string { + return dataset.displayName != null && dataset.displayName !== "" + ? dataset.displayName + : dataset.name; +} + +function getDetails(dataset: APIDataset): ExtendedDatasetDetails { + const { dataSource, details } = dataset; + return { ...details, scale: formatScale(dataSource.scale), name: getDisplayName(dataset) }; +} + +function ThumbnailAndDescription({ + thumbnailURL, + description, + datasetDetails, + publicationName, + datasetId, + segmentationThumbnailURL, +}: { + thumbnailURL: string, + publicationName: string, + datasetId: APIDatasetId, + description: React.Element<*> | string, + datasetDetails: ExtendedDatasetDetails, + segmentationThumbnailURL: ?string, +}) { + const details = datasetDetails; + return ( + <React.Fragment> + <div className="dataset-description"> + <div className="description-flex"> + <h3 style={{ fontSize: 20 }}>{publicationName}</h3> + <div className="dataset-description-body">{description}</div> + </div> + </div> + <span className="dataset-thumbnail"> + <Link to={`/datasets/${datasetId.owningOrganization}/${datasetId.name}/view`}> + <div className="dataset-click-hint">Click To View</div> + <div + className="dataset-thumbnail-image" + style={{ + backgroundImage: `url('${thumbnailURL}?w=${thumbnailDimension}&h=${thumbnailDimension}')`, + }} + /> + {segmentationThumbnailURL ? ( + <div + className="dataset-thumbnail-image segmentation" + style={{ + backgroundImage: `url('${segmentationThumbnailURL}?w=${thumbnailDimension}&h=${thumbnailDimension}')`, + }} + /> + ) : null} + <div className="dataset-thumbnail-overlay"> + <div + style={{ + textTransform: "uppercase", + fontSize: 16, + }} + > + {details.name} + </div> + <div> + {details.species && ( + <div + style={{ + fontSize: 18, + fontWeight: 700, + display: "inline", + }} + > + {details.species} + </div> + )} + {details.brainRegion && ( + <div + style={{ + display: "inline", + marginLeft: 5, + }} + > + {details.brainRegion} + </div> + )} + </div> + <div style={{ marginTop: "auto" }}> + {details.acquisition && ( + <div style={{ display: "inline-block", color: "rgba(200,200,200,0.85)" }}> + {details.acquisition} + </div> + )} + {details.scale && ( + <span style={{ float: "right", color: "rgba(200,200,200,0.85)" }}> + Scale: {details.scale} + </span> + )} + </div> + </div> + </Link> + </span> + </React.Fragment> + ); +} + +type Props = { datasets: Array<APIDataset> }; +type State = { activeDataset: APIDataset }; + +class PublicationCard extends React.PureComponent<Props, State> { + state = { + activeDataset: this.props.datasets[0], + }; + + render() { + const { datasets } = this.props; + const { activeDataset } = this.state; + const { publication } = activeDataset; + // This method will only be called for datasets with a publication, but Flow doesn't know that + if (publication == null) throw Error("Assertion Error: Dataset has no associated publication."); + + const descriptionComponent = ( + <div style={{ display: "flex", flexDirection: "column", height: "100%" }}> + <span style={{ marginBottom: 16 }}> + <Markdown + source={publication.description} + options={{ html: false, breaks: true, linkify: true }} + /> + </span> + <div style={{ marginTop: "auto" }}> + <span style={{ fontSize: 14, textTransform: "uppercase" }}>Published Datasets </span> + <div + className="mini-dataset-thumbnail-grid" + style={{ gridTemplateColumns: `repeat(auto-fill, ${miniThumbnailDimension}px)` }} + > + {datasets.map(dataset => { + const datasetIdString = `${dataset.owningOrganization}/${dataset.name}`; + return ( + <Link to={`/datasets/${datasetIdString}/view`} key={datasetIdString}> + <Button + className={classNames("mini-dataset-thumbnail", { + active: dataset.name === activeDataset.name, + })} + title="Click To View" + style={{ + background: `url('${getThumbnailURL( + dataset, + )}?w=${miniThumbnailDimension}&h=${miniThumbnailDimension}')`, + width: `${miniThumbnailDimension}px`, + height: `${miniThumbnailDimension}px`, + }} + onMouseEnter={() => this.setState({ activeDataset: dataset })} + > + <div + className="mini-dataset-thumbnail segmentation" + style={{ + background: `url('${getSegmentationThumbnailURL( + dataset, + )}?w=${miniThumbnailDimension}&h=${miniThumbnailDimension}')`, + }} + /> + </Button> + </Link> + ); + })} + </div> + </div> + </div> + ); + + return ( + <Card bodyStyle={{ padding: 0 }} className="spotlight-item-card" bordered={false}> + <ThumbnailAndDescription + thumbnailURL={getThumbnailURL(activeDataset)} + segmentationThumbnailURL={ + hasSegmentation(activeDataset) ? getSegmentationThumbnailURL(activeDataset) : null + } + publicationName={publication.title} + datasetId={{ + name: activeDataset.name, + owningOrganization: activeDataset.owningOrganization, + }} + description={descriptionComponent} + datasetDetails={getDetails(activeDataset)} + /> + </Card> + ); + } +} + +export default PublicationCard; diff --git a/app/assets/javascripts/dashboard/publication_view.js b/app/assets/javascripts/dashboard/publication_view.js new file mode 100644 index 00000000000..ecfe18f55cf --- /dev/null +++ b/app/assets/javascripts/dashboard/publication_view.js @@ -0,0 +1,65 @@ +// @flow +import * as React from "react"; +import _ from "lodash"; +import { List } from "antd"; + +import type { APIDataset, APIMaybeUnimportedDataset } from "admin/api_flow_types"; +import PublicationCard from "dashboard/publication_card"; +import * as Utils from "libs/utils"; + +const gridValues = { + gutter: 24, + xs: 1, + sm: 1, + md: 1, + lg: 1, + xl: 2, + xxl: 2, +}; + +type Props = { + datasets: Array<APIMaybeUnimportedDataset>, + searchQuery: string, +}; + +class PublicationView extends React.PureComponent<Props> { + render() { + // $FlowFixMe flow doesn't check that after filtering there are only imported datasets left + const activeDatasets: Array<APIDataset> = this.props.datasets.filter(ds => ds.isActive); + const filteredDatasets = Utils.filterWithSearchQueryAND( + activeDatasets, + ["name", "description", "details"], + this.props.searchQuery, + ); + + const datasetsByPublication = _.chain(filteredDatasets) + .filter(dataset => dataset.publication != null) + .groupBy("publication.id") + .values() + .sort( + // Sort publication groups by publication creation date + Utils.compareBy( + ([]: Array<APIDataset>), + datasets => datasets[0].publication.publicationDate, + false, + ), + ); + + return ( + <React.Fragment> + <List + grid={gridValues} + dataSource={datasetsByPublication} + locale={{ emptyText: "No featured publications." }} + renderItem={datasets => ( + <List.Item key={datasets[0].publication.id}> + <PublicationCard className="dataset-panel" datasets={datasets} /> + </List.Item> + )} + /> + </React.Fragment> + ); + } +} + +export default PublicationView; diff --git a/app/assets/javascripts/dashboard/spotlight_view.js b/app/assets/javascripts/dashboard/spotlight_view.js index 44b54ff5126..b420aff45ae 100644 --- a/app/assets/javascripts/dashboard/spotlight_view.js +++ b/app/assets/javascripts/dashboard/spotlight_view.js @@ -1,6 +1,6 @@ // @flow import { Link, type RouterHistory, withRouter } from "react-router-dom"; -import { Spin, Layout, Button, Row, Col } from "antd"; +import { Spin, Layout, Button, Row, Col, Input } from "antd"; import { connect } from "react-redux"; import * as React from "react"; @@ -8,10 +8,11 @@ import type { APIMaybeUnimportedDataset, APIUser } from "admin/api_flow_types"; import type { OxalisState } from "oxalis/store"; import { getOrganizations, getDatasets } from "admin/admin_rest_api"; import { handleGenericError } from "libs/error_handling"; -import GalleryDatasetView from "dashboard/gallery_dataset_view"; +import PublicationView from "dashboard/publication_view"; import features from "features"; const { Content, Footer } = Layout; +const { Search } = Input; const SimpleHeader = () => ( <div id="oxalis-header"> @@ -25,7 +26,12 @@ const WelcomeHeader = ({ history }) => ( backgroundImage: "url(/images/cover.jpg)", }} > - <div style={{ backgroundColor: "rgba(88, 88, 88, 0.5)" }}> + <div + style={{ + backgroundColor: "rgba(88, 88, 88, 0.4)", + backgroundImage: "linear-gradient(to bottom, #449efd7a 0%, #041a4abf 85%, #00050fc2 100%)", + }} + > <div style={{ maxWidth: 1300, @@ -43,15 +49,27 @@ const WelcomeHeader = ({ history }) => ( /> </Col> <Col span={16}> - <p style={{ fontSize: 58, textShadow: "0px 1px 6px #00000061" }}> + <p + style={{ + fontSize: 58, + textShadow: "rgba(0, 0, 0, 0.38) 0px 1px 6px", + textAlign: "left", + fontWeight: 300, + marginLeft: 56, + }} + > Welcome to webKnossos </p> <p style={{ fontSize: 20, - textShadow: "0px 1px 6px #00000061", - color: "rgb(245, 245, 245)", + textShadow: "rgba(0, 0, 0, 0.38) 0px 1px 6px", + color: "rgb(243, 243, 248)", padding: "40px 60px", + textAlign: "left", + lineHeight: 1.5, + paddingTop: 10, + marginTop: 0, }} > webKnossos is an in-browser annotation tool for 3D electron microscopic data that @@ -113,6 +131,7 @@ type State = { datasets: Array<APIMaybeUnimportedDataset>, hasOrganizations: boolean, isLoading: boolean, + searchQuery: string, }; class SpotlightView extends React.PureComponent<Props, State> { @@ -120,6 +139,7 @@ class SpotlightView extends React.PureComponent<Props, State> { datasets: [], hasOrganizations: true, isLoading: true, + searchQuery: "", }; componentDidMount() { @@ -138,7 +158,21 @@ class SpotlightView extends React.PureComponent<Props, State> { } } + handleSearch = (event: SyntheticInputEvent<>) => { + this.setState({ searchQuery: event.target.value }); + }; + render() { + const search = ( + <Search + style={{ width: 200, float: "right" }} + placeholder="Search Dataset" + onPressEnter={this.handleSearch} + onChange={this.handleSearch} + value={this.state.searchQuery} + /> + ); + return ( <Layout> {this.props.activeUser == null && @@ -148,6 +182,9 @@ class SpotlightView extends React.PureComponent<Props, State> { <SimpleHeader /> )} <Content style={{ padding: 50 }}> + <div className="pull-right">{search}</div> + <h3>Publications</h3> + <div className="clearfix" style={{ margin: "20px 0px" }} /> <Spin size="large" spinning={this.state.isLoading}> <div style={{ minHeight: "100px" }}> {this.state.datasets.length === 0 && !this.state.isLoading ? ( @@ -157,7 +194,10 @@ class SpotlightView extends React.PureComponent<Props, State> { in action. </p> ) : ( - <GalleryDatasetView datasets={this.state.datasets} searchQuery="" /> + <PublicationView + datasets={this.state.datasets} + searchQuery={this.state.searchQuery} + /> )} </div> </Spin> diff --git a/app/assets/javascripts/oxalis/store.js b/app/assets/javascripts/oxalis/store.js index 1e4b9e83ce8..891a5e10bf6 100644 --- a/app/assets/javascripts/oxalis/store.js +++ b/app/assets/javascripts/oxalis/store.js @@ -462,6 +462,7 @@ export const defaultState: OxalisState = { team: "", }, }, + details: null, isPublic: false, isActive: true, isEditable: true, @@ -478,6 +479,7 @@ export const defaultState: OxalisState = { lastUsedByUser: 0, isForeign: false, sortingKey: 123, + publication: null, }, tracing: { ...initialAnnotationInfo, diff --git a/app/assets/javascripts/test/snapshots/public/test-bundle/test/enzyme/snapshot.e2e.js.md b/app/assets/javascripts/test/snapshots/public/test-bundle/test/enzyme/snapshot.e2e.js.md index 87b4a2d7e3a..699141a928c 100644 --- a/app/assets/javascripts/test/snapshots/public/test-bundle/test/enzyme/snapshot.e2e.js.md +++ b/app/assets/javascripts/test/snapshots/public/test-bundle/test/enzyme/snapshot.e2e.js.md @@ -20,10 +20,10 @@ Generated by [AVA](https://ava.li). <DialogWrap title="Import 0 NML file(s)" visible={false} onCancel={[Function: onCancel]} footer={{...}} prefixCls="ant-modal" width={520} transitionName="zoom" maskTransitionName="fade" confirmLoading={false} okType="primary" mousePosition={[undefined]} onClose={[Function]} />␊ </Modal>␊ <div className="container">␊ - <Tabs activeKey="datasets" onChange={[Function: onTabChange]} style={{...}} prefixCls="ant-tabs" hideAdd={false}>␊ - <Tabs activeKey="datasets" onChange={[Function]} style={{...}} prefixCls="ant-tabs" hideAdd={false} className="ant-tabs-line" tabBarPosition="top" renderTabBar={[Function: renderTabBar]} renderTabContent={[Function: renderTabContent]} destroyInactiveTabPane={false}>␊ + <Tabs activeKey="publications" onChange={[Function: onTabChange]} style={{...}} prefixCls="ant-tabs" hideAdd={false}>␊ + <Tabs activeKey="publications" onChange={[Function]} style={{...}} prefixCls="ant-tabs" hideAdd={false} className="ant-tabs-line" tabBarPosition="top" renderTabBar={[Function: renderTabBar]} renderTabContent={[Function: renderTabContent]} destroyInactiveTabPane={false}>␊ <div className="ant-tabs ant-tabs-top ant-tabs-line" style={{...}}>␊ - <ScrollableInkTabBar inkBarAnimated={true} extraContent={{...}} onTabClick={[Function]} onPrevClick={[Function: onPrevClick]} onNextClick={[Function: onNextClick]} style={[undefined]} tabBarGutter={[undefined]} styles={{...}} scrollAnimated={true} prefixCls="ant-tabs" onKeyDown={[Function]} tabBarPosition="top" panels={{...}} activeKey="datasets">␊ + <ScrollableInkTabBar inkBarAnimated={true} extraContent={{...}} onTabClick={[Function]} onPrevClick={[Function: onPrevClick]} onNextClick={[Function: onNextClick]} style={[undefined]} tabBarGutter={[undefined]} styles={{...}} scrollAnimated={true} prefixCls="ant-tabs" onKeyDown={[Function]} tabBarPosition="top" panels={{...}} activeKey="publications">␊ <div role="tablist" className="ant-tabs-bar" tabIndex="0" onKeyDown={[Function]} style={[undefined]}>␊ <div className="ant-tabs-nav-container">␊ <span onClick={{...}} unselectable="unselectable" className="ant-tabs-tab-prev ant-tabs-tab-btn-disabled" onTransitionEnd={[Function: bound prevTransitionEnd]}>␊ @@ -37,7 +37,7 @@ Generated by [AVA](https://ava.li). <div className="ant-tabs-nav ant-tabs-nav-animated">␊ <div style={[undefined]} className="ant-tabs-ink-bar ant-tabs-ink-bar-animated" />␊ <div role="tab" aria-disabled="false" aria-selected="true" onClick={[Function: bound bound onTabClick]} className="ant-tabs-tab-active ant-tabs-tab" style={{...}}>␊ - Dataset Gallery␊ + Publications␊ </div>␊ <div role="tab" aria-disabled="false" aria-selected="false" onClick={[Function: bound bound onTabClick]} className=" ant-tabs-tab" style={{...}}>␊ Datasets␊ @@ -54,9 +54,9 @@ Generated by [AVA](https://ava.li). </div>␊ </div>␊ </ScrollableInkTabBar>␊ - <TabContent animated={true} animatedWithMargin={true} prefixCls="ant-tabs" tabBarPosition="top" activeKey="datasets" destroyInactiveTabPane={false} onChange={[Function]}>␊ + <TabContent animated={true} animatedWithMargin={true} prefixCls="ant-tabs" tabBarPosition="top" activeKey="publications" destroyInactiveTabPane={false} onChange={[Function]}>␊ <div className="ant-tabs-content ant-tabs-content-animated" style={{...}}>␊ - <TabPane tab="Dataset Gallery" placeholder={{...}} active={true} destroyInactiveTabPane={false} rootPrefixCls="ant-tabs">␊ + <TabPane tab="Publications" placeholder={{...}} active={true} destroyInactiveTabPane={false} rootPrefixCls="ant-tabs">␊ <div style={[undefined]} role="tabpanel" aria-hidden="false" className="ant-tabs-tabpane ant-tabs-tabpane-active">␊ <withRouter(DatasetView) user={{...}} onCheckDatasets={[Function]} datasets={{...}} isLoading={false} dataViewType="gallery">␊ <Route render={[Function: render]}>␊ @@ -101,7 +101,7 @@ Generated by [AVA](https://ava.li). </Search>␊ </div>␊ <h3 className="TestDatasetHeadline">␊ - Datasets␊ + Publications␊ </h3>␊ <div className="clearfix" style={{...}} />␊ <Spin size="large" spinning={false} prefixCls="ant-spin" wrapperClassName="">␊ @@ -109,91 +109,27 @@ Generated by [AVA](https://ava.li). <div className="ant-spin-nested-loading" style={{...}}>␊ <AnimateChild animation={{...}} transitionName="fade" transitionEnter={true} transitionAppear={false} transitionLeave={true}>␊ <div className="ant-spin-container">␊ - <GalleryDatasetView datasets={{...}} searchQuery="">␊ - <DatasetPanel showOrganizationHeader={false} croppedDatasetCount={6} className="dataset-panel" organizationName="Organization_X" datasets={{...}}>␊ - <div className="dataset-panel">␊ - <Row gutter={16}>␊ - <div className="ant-row" style={{...}}>␊ - <Col className="gallery-dataset-col" xs={24} sm={24} md={24} lg={12} xl={12} xxl={8} style={{...}}>␊ - <div style={{...}} className="gallery-dataset-col ant-col-xs-24 ant-col-sm-24 ant-col-md-24 ant-col-lg-12 ant-col-xl-12 ant-col-xxl-8">␊ - <a href="/datasets/Organization_X/e2006_knossos/view" title="View Dataset">␊ - <Card bodyStyle={{...}} className="spotlight-item-card">␊ - <div className="ant-card spotlight-item-card ant-card-bordered">␊ - <div className="ant-card-body" style={{...}}>␊ - <ThumbnailAndDescriptionFromDataset dataset={{...}}>␊ - <ThumbnailAndDescription thumbnailURL="/api/datasets/Organization_X/e2006_knossos/layers/color/thumbnail" name="e2006_knossos" description={{...}} segmentationThumbnailURL="/api/datasets/Organization_X/e2006_knossos/layers/segmentation/thumbnail">␊ - <span className="dataset-thumbnail" title="Click to view dataset">␊ - <div className="dataset-thumbnail-image" style={{...}} />␊ - <div className="dataset-thumbnail-image segmentation" style={{...}} />␊ - </span>␊ - <div className="dataset-description">␊ - <div className="description-flex">␊ - <h3>␊ - e2006_knossos␊ - </h3>␊ - <div className="dataset-description-body">␊ - <div>␊ - <p>␊ - Scale: ␊ - 16.5 × 16.5 × 25 nm³␊ - </p>␊ - <p>␊ - Original data and segmentation␊ - </p>␊ - </div>␊ - </div>␊ - </div>␊ - </div>␊ - </ThumbnailAndDescription>␊ - </ThumbnailAndDescriptionFromDataset>␊ - </div>␊ - </div>␊ - </Card>␊ - </a>␊ - </div>␊ - </Col>␊ - <Col className="gallery-dataset-col" xs={24} sm={24} md={24} lg={12} xl={12} xxl={8} style={{...}}>␊ - <div style={{...}} className="gallery-dataset-col ant-col-xs-24 ant-col-sm-24 ant-col-md-24 ant-col-lg-12 ant-col-xl-12 ant-col-xxl-8">␊ - <a href="/datasets/Organization_X/confocal-multi_knossos/view" title="View Dataset">␊ - <Card bodyStyle={{...}} className="spotlight-item-card">␊ - <div className="ant-card spotlight-item-card ant-card-bordered">␊ - <div className="ant-card-body" style={{...}}>␊ - <ThumbnailAndDescriptionFromDataset dataset={{...}}>␊ - <ThumbnailAndDescription thumbnailURL="/api/datasets/Organization_X/confocal-multi_knossos/layers/color_1/thumbnail" name="confocal-multi_knossos" description={{...}} segmentationThumbnailURL={{...}}>␊ - <span className="dataset-thumbnail" title="Click to view dataset">␊ - <div className="dataset-thumbnail-image" style={{...}} />␊ - </span>␊ - <div className="dataset-description">␊ - <div className="description-flex">␊ - <h3>␊ - confocal-multi_knossos␊ - </h3>␊ - <div className="dataset-description-body">␊ - <div>␊ - <p>␊ - Scale: ␊ - 22 × 22 × 44.6 nm³␊ - </p>␊ - <p>␊ - Original data␊ - </p>␊ - </div>␊ - </div>␊ - </div>␊ - </div>␊ - </ThumbnailAndDescription>␊ - </ThumbnailAndDescriptionFromDataset>␊ - </div>␊ + <PublicationView datasets={{...}} searchQuery="">␊ + <List grid={{...}} dataSource={{...}} locale={{...}} renderItem={[Function: renderItem]} prefixCls="ant-list" bordered={false} split={true} loading={false} pagination={false}>␊ + <div className="ant-list ant-list-split ant-list-grid">␊ + <Spin spinning={false} prefixCls="ant-spin" size="default" wrapperClassName="">␊ + <Animate component="div" className="ant-spin-nested-loading" style={{...}} transitionName="fade" animation={{...}} componentProps={{...}} transitionEnter={true} transitionLeave={true} transitionAppear={false} onEnd={[Function: noop]} onEnter={[Function: noop]} onLeave={[Function: noop]} onAppear={[Function: noop]}>␊ + <div className="ant-spin-nested-loading" style={{...}}>␊ + <AnimateChild animation={{...}} transitionName="fade" transitionEnter={true} transitionAppear={false} transitionLeave={true}>␊ + <div className="ant-spin-container">␊ + <LocaleReceiver componentName="Table" defaultLocale={{...}}>␊ + <div className="ant-list-empty-text">␊ + No featured publications.␊ </div>␊ - </Card>␊ - </a>␊ + </LocaleReceiver>␊ </div>␊ - </Col>␊ + </AnimateChild>␊ </div>␊ - </Row>␊ + </Animate>␊ + </Spin>␊ </div>␊ - </DatasetPanel>␊ - </GalleryDatasetView>␊ + </List>␊ + </PublicationView>␊ </div>␊ </AnimateChild>␊ </div>␊ @@ -265,7 +201,7 @@ Generated by [AVA](https://ava.li). <div className="ant-tabs-nav ant-tabs-nav-animated">␊ <div style={[undefined]} className="ant-tabs-ink-bar ant-tabs-ink-bar-animated" />␊ <div role="tab" aria-disabled="false" aria-selected="false" onClick={[Function: bound bound onTabClick]} className=" ant-tabs-tab" style={{...}}>␊ - Dataset Gallery␊ + Publications␊ </div>␊ <div role="tab" aria-disabled="false" aria-selected="true" onClick={[Function: bound bound onTabClick]} className="ant-tabs-tab-active ant-tabs-tab" style={{...}}>␊ Datasets␊ @@ -284,7 +220,7 @@ Generated by [AVA](https://ava.li). </ScrollableInkTabBar>␊ <TabContent animated={true} animatedWithMargin={true} prefixCls="ant-tabs" tabBarPosition="top" activeKey="advanced-datasets" destroyInactiveTabPane={false} onChange={[Function]}>␊ <div className="ant-tabs-content ant-tabs-content-animated" style={{...}}>␊ - <TabPane tab="Dataset Gallery" placeholder={{...}} active={false} destroyInactiveTabPane={false} rootPrefixCls="ant-tabs">␊ + <TabPane tab="Publications" placeholder={{...}} active={false} destroyInactiveTabPane={false} rootPrefixCls="ant-tabs">␊ <div style={[undefined]} role="tabpanel" aria-hidden="true" className="ant-tabs-tabpane ant-tabs-tabpane-inactive">␊ <withRouter(DatasetView) user={{...}} onCheckDatasets={[Function]} datasets={{...}} isLoading={false} dataViewType="gallery">␊ <Route render={[Function: render]}>␊ @@ -329,7 +265,7 @@ Generated by [AVA](https://ava.li). </Search>␊ </div>␊ <h3 className="TestDatasetHeadline">␊ - Datasets␊ + Publications␊ </h3>␊ <div className="clearfix" style={{...}} />␊ <Spin size="large" spinning={false} prefixCls="ant-spin" wrapperClassName="">␊ @@ -337,91 +273,27 @@ Generated by [AVA](https://ava.li). <div className="ant-spin-nested-loading" style={{...}}>␊ <AnimateChild animation={{...}} transitionName="fade" transitionEnter={true} transitionAppear={false} transitionLeave={true}>␊ <div className="ant-spin-container">␊ - <GalleryDatasetView datasets={{...}} searchQuery="">␊ - <DatasetPanel showOrganizationHeader={false} croppedDatasetCount={6} className="dataset-panel" organizationName="Organization_X" datasets={{...}}>␊ - <div className="dataset-panel">␊ - <Row gutter={16}>␊ - <div className="ant-row" style={{...}}>␊ - <Col className="gallery-dataset-col" xs={24} sm={24} md={24} lg={12} xl={12} xxl={8} style={{...}}>␊ - <div style={{...}} className="gallery-dataset-col ant-col-xs-24 ant-col-sm-24 ant-col-md-24 ant-col-lg-12 ant-col-xl-12 ant-col-xxl-8">␊ - <a href="/datasets/Organization_X/e2006_knossos/view" title="View Dataset">␊ - <Card bodyStyle={{...}} className="spotlight-item-card">␊ - <div className="ant-card spotlight-item-card ant-card-bordered">␊ - <div className="ant-card-body" style={{...}}>␊ - <ThumbnailAndDescriptionFromDataset dataset={{...}}>␊ - <ThumbnailAndDescription thumbnailURL="/api/datasets/Organization_X/e2006_knossos/layers/color/thumbnail" name="e2006_knossos" description={{...}} segmentationThumbnailURL="/api/datasets/Organization_X/e2006_knossos/layers/segmentation/thumbnail">␊ - <span className="dataset-thumbnail" title="Click to view dataset">␊ - <div className="dataset-thumbnail-image" style={{...}} />␊ - <div className="dataset-thumbnail-image segmentation" style={{...}} />␊ - </span>␊ - <div className="dataset-description">␊ - <div className="description-flex">␊ - <h3>␊ - e2006_knossos␊ - </h3>␊ - <div className="dataset-description-body">␊ - <div>␊ - <p>␊ - Scale: ␊ - 16.5 × 16.5 × 25 nm³␊ - </p>␊ - <p>␊ - Original data and segmentation␊ - </p>␊ - </div>␊ - </div>␊ - </div>␊ - </div>␊ - </ThumbnailAndDescription>␊ - </ThumbnailAndDescriptionFromDataset>␊ - </div>␊ - </div>␊ - </Card>␊ - </a>␊ - </div>␊ - </Col>␊ - <Col className="gallery-dataset-col" xs={24} sm={24} md={24} lg={12} xl={12} xxl={8} style={{...}}>␊ - <div style={{...}} className="gallery-dataset-col ant-col-xs-24 ant-col-sm-24 ant-col-md-24 ant-col-lg-12 ant-col-xl-12 ant-col-xxl-8">␊ - <a href="/datasets/Organization_X/confocal-multi_knossos/view" title="View Dataset">␊ - <Card bodyStyle={{...}} className="spotlight-item-card">␊ - <div className="ant-card spotlight-item-card ant-card-bordered">␊ - <div className="ant-card-body" style={{...}}>␊ - <ThumbnailAndDescriptionFromDataset dataset={{...}}>␊ - <ThumbnailAndDescription thumbnailURL="/api/datasets/Organization_X/confocal-multi_knossos/layers/color_1/thumbnail" name="confocal-multi_knossos" description={{...}} segmentationThumbnailURL={{...}}>␊ - <span className="dataset-thumbnail" title="Click to view dataset">␊ - <div className="dataset-thumbnail-image" style={{...}} />␊ - </span>␊ - <div className="dataset-description">␊ - <div className="description-flex">␊ - <h3>␊ - confocal-multi_knossos␊ - </h3>␊ - <div className="dataset-description-body">␊ - <div>␊ - <p>␊ - Scale: ␊ - 22 × 22 × 44.6 nm³␊ - </p>␊ - <p>␊ - Original data␊ - </p>␊ - </div>␊ - </div>␊ - </div>␊ - </div>␊ - </ThumbnailAndDescription>␊ - </ThumbnailAndDescriptionFromDataset>␊ - </div>␊ + <PublicationView datasets={{...}} searchQuery="">␊ + <List grid={{...}} dataSource={{...}} locale={{...}} renderItem={[Function: renderItem]} prefixCls="ant-list" bordered={false} split={true} loading={false} pagination={false}>␊ + <div className="ant-list ant-list-split ant-list-grid">␊ + <Spin spinning={false} prefixCls="ant-spin" size="default" wrapperClassName="">␊ + <Animate component="div" className="ant-spin-nested-loading" style={{...}} transitionName="fade" animation={{...}} componentProps={{...}} transitionEnter={true} transitionLeave={true} transitionAppear={false} onEnd={[Function: noop]} onEnter={[Function: noop]} onLeave={[Function: noop]} onAppear={[Function: noop]}>␊ + <div className="ant-spin-nested-loading" style={{...}}>␊ + <AnimateChild animation={{...}} transitionName="fade" transitionEnter={true} transitionAppear={false} transitionLeave={true}>␊ + <div className="ant-spin-container">␊ + <LocaleReceiver componentName="Table" defaultLocale={{...}}>␊ + <div className="ant-list-empty-text">␊ + No featured publications.␊ </div>␊ - </Card>␊ - </a>␊ + </LocaleReceiver>␊ </div>␊ - </Col>␊ + </AnimateChild>␊ </div>␊ - </Row>␊ + </Animate>␊ + </Spin>␊ </div>␊ - </DatasetPanel>␊ - </GalleryDatasetView>␊ + </List>␊ + </PublicationView>␊ </div>␊ </AnimateChild>␊ </div>␊ @@ -743,12 +615,14 @@ Generated by [AVA](https://ava.li). </Icon>␊ Reload␊ </a>␊ - <a href="/datasets/Organization_X/e2006_knossos/view" title="View Dataset">␊ + <Link to="/datasets/Organization_X/e2006_knossos/view" title="View Dataset" replace={false}>␊ + <a title="View Dataset" onClick={[Function]} href="/datasets/Organization_X/e2006_knossos/view">␊ <Icon type="eye-o">␊ <i className="anticon anticon-eye-o" />␊ </Icon>␊ View␊ </a>␊ + </Link>␊ <a href="#" onClick={[Function: onClick]} title="Create Skeleton Tracing">␊ <img src="/images/skeleton.svg" alt="skeleton icon" style={{...}} />␊ Start Skeleton Tracing␊ @@ -918,12 +792,14 @@ Generated by [AVA](https://ava.li). </Icon>␊ Reload␊ </a>␊ - <a href="/datasets/Organization_X/confocal-multi_knossos/view" title="View Dataset">␊ + <Link to="/datasets/Organization_X/confocal-multi_knossos/view" title="View Dataset" replace={false}>␊ + <a title="View Dataset" onClick={[Function]} href="/datasets/Organization_X/confocal-multi_knossos/view">␊ <Icon type="eye-o">␊ <i className="anticon anticon-eye-o" />␊ </Icon>␊ View␊ </a>␊ + </Link>␊ <a href="#" onClick={[Function: onClick]} title="Create Skeleton Tracing">␊ <img src="/images/skeleton.svg" alt="skeleton icon" style={{...}} />␊ Start Skeleton Tracing␊ @@ -1512,7 +1388,7 @@ Generated by [AVA](https://ava.li). <div className="ant-tabs-nav ant-tabs-nav-animated">␊ <div style={[undefined]} className="ant-tabs-ink-bar ant-tabs-ink-bar-animated" />␊ <div role="tab" aria-disabled="false" aria-selected="false" onClick={[Function: bound bound onTabClick]} className=" ant-tabs-tab" style={{...}}>␊ - Dataset Gallery␊ + Publications␊ </div>␊ <div role="tab" aria-disabled="false" aria-selected="false" onClick={[Function: bound bound onTabClick]} className=" ant-tabs-tab" style={{...}}>␊ Datasets␊ @@ -1531,7 +1407,7 @@ Generated by [AVA](https://ava.li). </ScrollableInkTabBar>␊ <TabContent animated={true} animatedWithMargin={true} prefixCls="ant-tabs" tabBarPosition="top" activeKey="explorativeAnnotations" destroyInactiveTabPane={false} onChange={[Function]}>␊ <div className="ant-tabs-content ant-tabs-content-animated" style={{...}}>␊ - <TabPane tab="Dataset Gallery" placeholder={{...}} active={false} destroyInactiveTabPane={false} rootPrefixCls="ant-tabs">␊ + <TabPane tab="Publications" placeholder={{...}} active={false} destroyInactiveTabPane={false} rootPrefixCls="ant-tabs">␊ <div style={[undefined]} role="tabpanel" aria-hidden="true" className="ant-tabs-tabpane ant-tabs-tabpane-inactive">␊ <withRouter(DatasetView) user={{...}} onCheckDatasets={[Function]} datasets={{...}} isLoading={false} dataViewType="gallery">␊ <Route render={[Function: render]}>␊ @@ -1576,7 +1452,7 @@ Generated by [AVA](https://ava.li). </Search>␊ </div>␊ <h3 className="TestDatasetHeadline">␊ - Datasets␊ + Publications␊ </h3>␊ <div className="clearfix" style={{...}} />␊ <Spin size="large" spinning={false} prefixCls="ant-spin" wrapperClassName="">␊ @@ -1584,91 +1460,27 @@ Generated by [AVA](https://ava.li). <div className="ant-spin-nested-loading" style={{...}}>␊ <AnimateChild animation={{...}} transitionName="fade" transitionEnter={true} transitionAppear={false} transitionLeave={true}>␊ <div className="ant-spin-container">␊ - <GalleryDatasetView datasets={{...}} searchQuery="">␊ - <DatasetPanel showOrganizationHeader={false} croppedDatasetCount={6} className="dataset-panel" organizationName="Organization_X" datasets={{...}}>␊ - <div className="dataset-panel">␊ - <Row gutter={16}>␊ - <div className="ant-row" style={{...}}>␊ - <Col className="gallery-dataset-col" xs={24} sm={24} md={24} lg={12} xl={12} xxl={8} style={{...}}>␊ - <div style={{...}} className="gallery-dataset-col ant-col-xs-24 ant-col-sm-24 ant-col-md-24 ant-col-lg-12 ant-col-xl-12 ant-col-xxl-8">␊ - <a href="/datasets/Organization_X/e2006_knossos/view" title="View Dataset">␊ - <Card bodyStyle={{...}} className="spotlight-item-card">␊ - <div className="ant-card spotlight-item-card ant-card-bordered">␊ - <div className="ant-card-body" style={{...}}>␊ - <ThumbnailAndDescriptionFromDataset dataset={{...}}>␊ - <ThumbnailAndDescription thumbnailURL="/api/datasets/Organization_X/e2006_knossos/layers/color/thumbnail" name="e2006_knossos" description={{...}} segmentationThumbnailURL="/api/datasets/Organization_X/e2006_knossos/layers/segmentation/thumbnail">␊ - <span className="dataset-thumbnail" title="Click to view dataset">␊ - <div className="dataset-thumbnail-image" style={{...}} />␊ - <div className="dataset-thumbnail-image segmentation" style={{...}} />␊ - </span>␊ - <div className="dataset-description">␊ - <div className="description-flex">␊ - <h3>␊ - e2006_knossos␊ - </h3>␊ - <div className="dataset-description-body">␊ - <div>␊ - <p>␊ - Scale: ␊ - 16.5 × 16.5 × 25 nm³␊ - </p>␊ - <p>␊ - Original data and segmentation␊ - </p>␊ - </div>␊ - </div>␊ - </div>␊ - </div>␊ - </ThumbnailAndDescription>␊ - </ThumbnailAndDescriptionFromDataset>␊ - </div>␊ - </div>␊ - </Card>␊ - </a>␊ - </div>␊ - </Col>␊ - <Col className="gallery-dataset-col" xs={24} sm={24} md={24} lg={12} xl={12} xxl={8} style={{...}}>␊ - <div style={{...}} className="gallery-dataset-col ant-col-xs-24 ant-col-sm-24 ant-col-md-24 ant-col-lg-12 ant-col-xl-12 ant-col-xxl-8">␊ - <a href="/datasets/Organization_X/confocal-multi_knossos/view" title="View Dataset">␊ - <Card bodyStyle={{...}} className="spotlight-item-card">␊ - <div className="ant-card spotlight-item-card ant-card-bordered">␊ - <div className="ant-card-body" style={{...}}>␊ - <ThumbnailAndDescriptionFromDataset dataset={{...}}>␊ - <ThumbnailAndDescription thumbnailURL="/api/datasets/Organization_X/confocal-multi_knossos/layers/color_1/thumbnail" name="confocal-multi_knossos" description={{...}} segmentationThumbnailURL={{...}}>␊ - <span className="dataset-thumbnail" title="Click to view dataset">␊ - <div className="dataset-thumbnail-image" style={{...}} />␊ - </span>␊ - <div className="dataset-description">␊ - <div className="description-flex">␊ - <h3>␊ - confocal-multi_knossos␊ - </h3>␊ - <div className="dataset-description-body">␊ - <div>␊ - <p>␊ - Scale: ␊ - 22 × 22 × 44.6 nm³␊ - </p>␊ - <p>␊ - Original data␊ - </p>␊ - </div>␊ - </div>␊ - </div>␊ - </div>␊ - </ThumbnailAndDescription>␊ - </ThumbnailAndDescriptionFromDataset>␊ - </div>␊ + <PublicationView datasets={{...}} searchQuery="">␊ + <List grid={{...}} dataSource={{...}} locale={{...}} renderItem={[Function: renderItem]} prefixCls="ant-list" bordered={false} split={true} loading={false} pagination={false}>␊ + <div className="ant-list ant-list-split ant-list-grid">␊ + <Spin spinning={false} prefixCls="ant-spin" size="default" wrapperClassName="">␊ + <Animate component="div" className="ant-spin-nested-loading" style={{...}} transitionName="fade" animation={{...}} componentProps={{...}} transitionEnter={true} transitionLeave={true} transitionAppear={false} onEnd={[Function: noop]} onEnter={[Function: noop]} onLeave={[Function: noop]} onAppear={[Function: noop]}>␊ + <div className="ant-spin-nested-loading" style={{...}}>␊ + <AnimateChild animation={{...}} transitionName="fade" transitionEnter={true} transitionAppear={false} transitionLeave={true}>␊ + <div className="ant-spin-container">␊ + <LocaleReceiver componentName="Table" defaultLocale={{...}}>␊ + <div className="ant-list-empty-text">␊ + No featured publications.␊ </div>␊ - </Card>␊ - </a>␊ + </LocaleReceiver>␊ </div>␊ - </Col>␊ + </AnimateChild>␊ </div>␊ - </Row>␊ + </Animate>␊ + </Spin>␊ </div>␊ - </DatasetPanel>␊ - </GalleryDatasetView>␊ + </List>␊ + </PublicationView>␊ </div>␊ </AnimateChild>␊ </div>␊ @@ -1990,12 +1802,14 @@ Generated by [AVA](https://ava.li). </Icon>␊ Reload␊ </a>␊ - <a href="/datasets/Organization_X/e2006_knossos/view" title="View Dataset">␊ + <Link to="/datasets/Organization_X/e2006_knossos/view" title="View Dataset" replace={false}>␊ + <a title="View Dataset" onClick={[Function]} href="/datasets/Organization_X/e2006_knossos/view">␊ <Icon type="eye-o">␊ <i className="anticon anticon-eye-o" />␊ </Icon>␊ View␊ </a>␊ + </Link>␊ <a href="#" onClick={[Function: onClick]} title="Create Skeleton Tracing">␊ <img src="/images/skeleton.svg" alt="skeleton icon" style={{...}} />␊ Start Skeleton Tracing␊ @@ -2165,12 +1979,14 @@ Generated by [AVA](https://ava.li). </Icon>␊ Reload␊ </a>␊ - <a href="/datasets/Organization_X/confocal-multi_knossos/view" title="View Dataset">␊ + <Link to="/datasets/Organization_X/confocal-multi_knossos/view" title="View Dataset" replace={false}>␊ + <a title="View Dataset" onClick={[Function]} href="/datasets/Organization_X/confocal-multi_knossos/view">␊ <Icon type="eye-o">␊ <i className="anticon anticon-eye-o" />␊ </Icon>␊ View␊ </a>␊ + </Link>␊ <a href="#" onClick={[Function: onClick]} title="Create Skeleton Tracing">␊ <img src="/images/skeleton.svg" alt="skeleton icon" style={{...}} />␊ Start Skeleton Tracing␊ @@ -3748,7 +3564,7 @@ Generated by [AVA](https://ava.li). <div className="ant-tabs-nav ant-tabs-nav-animated">␊ <div style={[undefined]} className="ant-tabs-ink-bar ant-tabs-ink-bar-animated" />␊ <div role="tab" aria-disabled="false" aria-selected="false" onClick={[Function: bound bound onTabClick]} className=" ant-tabs-tab" style={{...}}>␊ - Dataset Gallery␊ + Publications␊ </div>␊ <div role="tab" aria-disabled="false" aria-selected="false" onClick={[Function: bound bound onTabClick]} className=" ant-tabs-tab" style={{...}}>␊ Datasets␊ @@ -3767,7 +3583,7 @@ Generated by [AVA](https://ava.li). </ScrollableInkTabBar>␊ <TabContent animated={true} animatedWithMargin={true} prefixCls="ant-tabs" tabBarPosition="top" activeKey="tasks" destroyInactiveTabPane={false} onChange={[Function]}>␊ <div className="ant-tabs-content ant-tabs-content-animated" style={{...}}>␊ - <TabPane tab="Dataset Gallery" placeholder={{...}} active={false} destroyInactiveTabPane={false} rootPrefixCls="ant-tabs">␊ + <TabPane tab="Publications" placeholder={{...}} active={false} destroyInactiveTabPane={false} rootPrefixCls="ant-tabs">␊ <div style={[undefined]} role="tabpanel" aria-hidden="true" className="ant-tabs-tabpane ant-tabs-tabpane-inactive">␊ <withRouter(DatasetView) user={{...}} onCheckDatasets={[Function]} datasets={{...}} isLoading={false} dataViewType="gallery">␊ <Route render={[Function: render]}>␊ @@ -3812,7 +3628,7 @@ Generated by [AVA](https://ava.li). </Search>␊ </div>␊ <h3 className="TestDatasetHeadline">␊ - Datasets␊ + Publications␊ </h3>␊ <div className="clearfix" style={{...}} />␊ <Spin size="large" spinning={false} prefixCls="ant-spin" wrapperClassName="">␊ @@ -3820,91 +3636,27 @@ Generated by [AVA](https://ava.li). <div className="ant-spin-nested-loading" style={{...}}>␊ <AnimateChild animation={{...}} transitionName="fade" transitionEnter={true} transitionAppear={false} transitionLeave={true}>␊ <div className="ant-spin-container">␊ - <GalleryDatasetView datasets={{...}} searchQuery="">␊ - <DatasetPanel showOrganizationHeader={false} croppedDatasetCount={6} className="dataset-panel" organizationName="Organization_X" datasets={{...}}>␊ - <div className="dataset-panel">␊ - <Row gutter={16}>␊ - <div className="ant-row" style={{...}}>␊ - <Col className="gallery-dataset-col" xs={24} sm={24} md={24} lg={12} xl={12} xxl={8} style={{...}}>␊ - <div style={{...}} className="gallery-dataset-col ant-col-xs-24 ant-col-sm-24 ant-col-md-24 ant-col-lg-12 ant-col-xl-12 ant-col-xxl-8">␊ - <a href="/datasets/Organization_X/e2006_knossos/view" title="View Dataset">␊ - <Card bodyStyle={{...}} className="spotlight-item-card">␊ - <div className="ant-card spotlight-item-card ant-card-bordered">␊ - <div className="ant-card-body" style={{...}}>␊ - <ThumbnailAndDescriptionFromDataset dataset={{...}}>␊ - <ThumbnailAndDescription thumbnailURL="/api/datasets/Organization_X/e2006_knossos/layers/color/thumbnail" name="e2006_knossos" description={{...}} segmentationThumbnailURL="/api/datasets/Organization_X/e2006_knossos/layers/segmentation/thumbnail">␊ - <span className="dataset-thumbnail" title="Click to view dataset">␊ - <div className="dataset-thumbnail-image" style={{...}} />␊ - <div className="dataset-thumbnail-image segmentation" style={{...}} />␊ - </span>␊ - <div className="dataset-description">␊ - <div className="description-flex">␊ - <h3>␊ - e2006_knossos␊ - </h3>␊ - <div className="dataset-description-body">␊ - <div>␊ - <p>␊ - Scale: ␊ - 16.5 × 16.5 × 25 nm³␊ - </p>␊ - <p>␊ - Original data and segmentation␊ - </p>␊ - </div>␊ - </div>␊ - </div>␊ - </div>␊ - </ThumbnailAndDescription>␊ - </ThumbnailAndDescriptionFromDataset>␊ - </div>␊ - </div>␊ - </Card>␊ - </a>␊ - </div>␊ - </Col>␊ - <Col className="gallery-dataset-col" xs={24} sm={24} md={24} lg={12} xl={12} xxl={8} style={{...}}>␊ - <div style={{...}} className="gallery-dataset-col ant-col-xs-24 ant-col-sm-24 ant-col-md-24 ant-col-lg-12 ant-col-xl-12 ant-col-xxl-8">␊ - <a href="/datasets/Organization_X/confocal-multi_knossos/view" title="View Dataset">␊ - <Card bodyStyle={{...}} className="spotlight-item-card">␊ - <div className="ant-card spotlight-item-card ant-card-bordered">␊ - <div className="ant-card-body" style={{...}}>␊ - <ThumbnailAndDescriptionFromDataset dataset={{...}}>␊ - <ThumbnailAndDescription thumbnailURL="/api/datasets/Organization_X/confocal-multi_knossos/layers/color_1/thumbnail" name="confocal-multi_knossos" description={{...}} segmentationThumbnailURL={{...}}>␊ - <span className="dataset-thumbnail" title="Click to view dataset">␊ - <div className="dataset-thumbnail-image" style={{...}} />␊ - </span>␊ - <div className="dataset-description">␊ - <div className="description-flex">␊ - <h3>␊ - confocal-multi_knossos␊ - </h3>␊ - <div className="dataset-description-body">␊ - <div>␊ - <p>␊ - Scale: ␊ - 22 × 22 × 44.6 nm³␊ - </p>␊ - <p>␊ - Original data␊ - </p>␊ - </div>␊ - </div>␊ - </div>␊ - </div>␊ - </ThumbnailAndDescription>␊ - </ThumbnailAndDescriptionFromDataset>␊ - </div>␊ + <PublicationView datasets={{...}} searchQuery="">␊ + <List grid={{...}} dataSource={{...}} locale={{...}} renderItem={[Function: renderItem]} prefixCls="ant-list" bordered={false} split={true} loading={false} pagination={false}>␊ + <div className="ant-list ant-list-split ant-list-grid">␊ + <Spin spinning={false} prefixCls="ant-spin" size="default" wrapperClassName="">␊ + <Animate component="div" className="ant-spin-nested-loading" style={{...}} transitionName="fade" animation={{...}} componentProps={{...}} transitionEnter={true} transitionLeave={true} transitionAppear={false} onEnd={[Function: noop]} onEnter={[Function: noop]} onLeave={[Function: noop]} onAppear={[Function: noop]}>␊ + <div className="ant-spin-nested-loading" style={{...}}>␊ + <AnimateChild animation={{...}} transitionName="fade" transitionEnter={true} transitionAppear={false} transitionLeave={true}>␊ + <div className="ant-spin-container">␊ + <LocaleReceiver componentName="Table" defaultLocale={{...}}>␊ + <div className="ant-list-empty-text">␊ + No featured publications.␊ </div>␊ - </Card>␊ - </a>␊ + </LocaleReceiver>␊ </div>␊ - </Col>␊ + </AnimateChild>␊ </div>␊ - </Row>␊ + </Animate>␊ + </Spin>␊ </div>␊ - </DatasetPanel>␊ - </GalleryDatasetView>␊ + </List>␊ + </PublicationView>␊ </div>␊ </AnimateChild>␊ </div>␊ @@ -4226,12 +3978,14 @@ Generated by [AVA](https://ava.li). </Icon>␊ Reload␊ </a>␊ - <a href="/datasets/Organization_X/e2006_knossos/view" title="View Dataset">␊ + <Link to="/datasets/Organization_X/e2006_knossos/view" title="View Dataset" replace={false}>␊ + <a title="View Dataset" onClick={[Function]} href="/datasets/Organization_X/e2006_knossos/view">␊ <Icon type="eye-o">␊ <i className="anticon anticon-eye-o" />␊ </Icon>␊ View␊ </a>␊ + </Link>␊ <a href="#" onClick={[Function: onClick]} title="Create Skeleton Tracing">␊ <img src="/images/skeleton.svg" alt="skeleton icon" style={{...}} />␊ Start Skeleton Tracing␊ @@ -4401,12 +4155,14 @@ Generated by [AVA](https://ava.li). </Icon>␊ Reload␊ </a>␊ - <a href="/datasets/Organization_X/confocal-multi_knossos/view" title="View Dataset">␊ + <Link to="/datasets/Organization_X/confocal-multi_knossos/view" title="View Dataset" replace={false}>␊ + <a title="View Dataset" onClick={[Function]} href="/datasets/Organization_X/confocal-multi_knossos/view">␊ <Icon type="eye-o">␊ <i className="anticon anticon-eye-o" />␊ </Icon>␊ View␊ </a>␊ + </Link>␊ <a href="#" onClick={[Function: onClick]} title="Create Skeleton Tracing">␊ <img src="/images/skeleton.svg" alt="skeleton icon" style={{...}} />␊ Start Skeleton Tracing␊ diff --git a/app/assets/javascripts/test/snapshots/public/test-bundle/test/enzyme/snapshot.e2e.js.snap b/app/assets/javascripts/test/snapshots/public/test-bundle/test/enzyme/snapshot.e2e.js.snap index edd29621529..7b4c409b722 100644 Binary files a/app/assets/javascripts/test/snapshots/public/test-bundle/test/enzyme/snapshot.e2e.js.snap and b/app/assets/javascripts/test/snapshots/public/test-bundle/test/enzyme/snapshot.e2e.js.snap differ diff --git a/app/assets/stylesheets/_dashboard.less b/app/assets/stylesheets/_dashboard.less index 121a0a08fa7..5d690c56387 100644 --- a/app/assets/stylesheets/_dashboard.less +++ b/app/assets/stylesheets/_dashboard.less @@ -1,14 +1,5 @@ @smartphones: ~"only screen and (max-width: 600px)"; -.dataset-panel { - padding-bottom: 40px; - - .show-more-link { - padding-right: 24px; - float: right; - } -} - .dataset-description :first-child { margin-top: 0px; } @@ -22,6 +13,7 @@ } .dataset-thumbnail { + left: 50%; height: 100%; width: 50%; display: inline-block; @@ -37,13 +29,44 @@ .dataset-thumbnail-image { transition: all 0.3s ease-in-out; + background-size: cover; + width: 100%; + height: 100%; &.segmentation { - opacity: 0; + opacity: 0.2; + position: absolute; + left: 0; + top: 0; } } -.spotlight-item-card:hover .dataset-thumbnail-image.segmentation { - opacity: 0.2; +.dataset-thumbnail-overlay { + width: 100%; + height: 100px; + bottom: 0; + position: absolute; + background-color: rgba(0, 0, 0, 0.85); + padding: 5px 15px; + color: rgba(255, 255, 255, 0.95); + display: flex; + flex-direction: column; +} + +.dataset-click-hint { + opacity: 0; + width: 100%; + height: 100%; + position: absolute; + text-align: center; + color: white; + z-index: 100; + line-height: 400px; + font-size: 20px; + + &:hover { + background-color: rgba(0, 0, 0, 0.6); + opacity: 1; + } } .dataset-description { @@ -52,10 +75,10 @@ bottom: 0px; width: 50%; height: 100%; - padding-top: 16px; - padding-left: 16px; + padding: 16px; overflow-y: auto; background-color: white; + z-index: 1; @media @smartphones { padding-top: 24px; padding-left: 24px; @@ -85,7 +108,7 @@ .dataset-description-body { width: 100%; - height: 240px; + height: 100%; overflow-y: auto; @media @smartphones { height: 243px; @@ -134,13 +157,15 @@ } .spotlight-item-card { - height: 300px; + height: 400px; width: 100%; position: relative; overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + border-radius: 6px; &:hover { - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + box-shadow: 0 4px 6px hsla(0, 0%, 0%, 0.22), 0 5px 15px hsla(0, 0%, 0%, 0.1); transform: translateY(-2px); .dataset-thumbnail-image { @@ -159,6 +184,34 @@ } } +.mini-dataset-thumbnail-grid { + display: grid; + grid-gap: 10px; + padding: 5px; +} + +.mini-dataset-thumbnail { + opacity: 0.5; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + background-size: cover; + + &.segmentation { + opacity: 0.2 !important; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + } + &:hover, + &.active { + opacity: 1; + } + &:active { + transform: scale(1.05); + } +} + .dataset-actions { a { display: block;