mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-02 19:58:11 +08:00
Create dataset info drawer (#1805)
* create infoDrawer * create read/writes to redux store * reimplement reducer that vanished * remove aboutURL stuff from title * add formatting and style * s/length/size and make metadata items list items * remove comment * remove empty singletons * refactor into async react component * Clean up skeleton * swap out for loop for map * add comment * replace placeholder * switch ternary for `&&` * event handling fixes and PR feedback * add button and move click handler to button * ditch empty categories * move drawer button handling to redux * remove categorical move note * PR feedback from colin * update snapshot * remove hover state
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import React, { PureComponent } from "react";
|
||||
import { connect, shallowEqual } from "react-redux";
|
||||
import { Drawer, H3, H1, UL, Classes } from "@blueprintjs/core";
|
||||
import Async from "react-async";
|
||||
import {
|
||||
selectableCategoryNames,
|
||||
createCategorySummaryFromDfCol,
|
||||
} from "../../util/stateManager/controlsHelpers";
|
||||
|
||||
@connect((state) => {
|
||||
return {
|
||||
annoMatrix: state.annoMatrix,
|
||||
schema: state.annoMatrix.schema,
|
||||
datasetTitle: state.config?.displayNames?.dataset ?? "",
|
||||
aboutURL: state.config?.links?.["about-dataset"],
|
||||
isOpen: state.controls.datasetDrawer,
|
||||
};
|
||||
})
|
||||
class InfoDrawer extends PureComponent {
|
||||
static watchAsync(props, prevProps) {
|
||||
return !shallowEqual(props.watchProps, prevProps.watchProps);
|
||||
}
|
||||
|
||||
fetchAsyncProps = async (props) => {
|
||||
const { schema } = props.watchProps;
|
||||
const { annoMatrix } = this.props;
|
||||
|
||||
const allCategoryNames = selectableCategoryNames(schema).sort();
|
||||
|
||||
const nonUserAnnoCategories = allCategoryNames.map((catName) => {
|
||||
const isUserAnno = schema?.annotations?.obsByName[catName]?.writable;
|
||||
if (!isUserAnno) return annoMatrix.fetch("obs", catName);
|
||||
return null;
|
||||
});
|
||||
const singleValueCategories = (
|
||||
await Promise.all(nonUserAnnoCategories)
|
||||
).reduce((acc, categoryData, i) => {
|
||||
const catName = allCategoryNames[i];
|
||||
|
||||
const column = categoryData.icol(0);
|
||||
const colSchema = schema.annotations.obsByName[catName];
|
||||
|
||||
const categorySummary = createCategorySummaryFromDfCol(column, colSchema);
|
||||
|
||||
const { numCategoryValues } = categorySummary;
|
||||
// Add to the array if the category has only one value
|
||||
if (numCategoryValues === 1) {
|
||||
acc.set(catName, categorySummary.allCategoryValues[0]);
|
||||
}
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
return { singleValueCategories };
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch({ type: "toggle dataset drawer" });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { position, aboutURL, datasetTitle, schema, isOpen } = this.props;
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title="Dataset Overview"
|
||||
onClose={this.handleClose}
|
||||
{...{ isOpen, position }}
|
||||
>
|
||||
<Async
|
||||
watchFn={InfoDrawer.watchAsync}
|
||||
promiseFn={this.fetchAsyncProps}
|
||||
watchProps={{ schema }}
|
||||
>
|
||||
<Async.Pending>
|
||||
<InfoFormat skeleton {...{ datasetTitle, aboutURL }} />
|
||||
</Async.Pending>
|
||||
<Async.Rejected>
|
||||
{(error) => {
|
||||
console.error(error);
|
||||
return <span>Failed to load info</span>;
|
||||
}}
|
||||
</Async.Rejected>
|
||||
<Async.Fulfilled>
|
||||
{(asyncProps) => {
|
||||
const { singleValueCategories } = asyncProps;
|
||||
return (
|
||||
<InfoFormat
|
||||
{...{ datasetTitle, aboutURL, singleValueCategories }}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Async.Fulfilled>
|
||||
</Async>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const NUM_CATEGORIES = 8;
|
||||
|
||||
const singleValueCategoriesPlaceholder = Array.from(Array(NUM_CATEGORIES)).map(
|
||||
(_, index) => {
|
||||
return [index, index];
|
||||
}
|
||||
);
|
||||
|
||||
const InfoFormat = ({
|
||||
datasetTitle,
|
||||
singleValueCategories = new Map(singleValueCategoriesPlaceholder),
|
||||
aboutURL = "thisisabouthtelengthofaurl",
|
||||
skeleton = false,
|
||||
}) => {
|
||||
return (
|
||||
<div style={{ margin: 24 }}>
|
||||
<H1 className={skeleton ? Classes.SKELETON : null}>{datasetTitle}</H1>
|
||||
{singleValueCategories.size > 0 && (
|
||||
<>
|
||||
<H3 className={skeleton ? Classes.SKELETON : null}>
|
||||
Dataset Metadata
|
||||
</H3>
|
||||
<UL>
|
||||
{Array.from(singleValueCategories).map((pair) => {
|
||||
if (!pair[1] || pair[1] === "") return null;
|
||||
return (
|
||||
<li
|
||||
className={skeleton ? Classes.SKELETON : null}
|
||||
key={pair[0]}
|
||||
>{`${pair[0]}: ${pair[1]}`}</li>
|
||||
);
|
||||
})}
|
||||
</UL>
|
||||
</>
|
||||
)}
|
||||
{aboutURL && (
|
||||
<>
|
||||
<H3 className={skeleton ? Classes.SKELETON : null}>More Info</H3>
|
||||
<a
|
||||
className={skeleton ? Classes.SKELETON : null}
|
||||
href={aboutURL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{aboutURL}
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default InfoDrawer;
|
||||
Reference in New Issue
Block a user