Merge branch 'main' into colinmegill/geneset-prototype

This commit is contained in:
Colin Megill
2020-08-25 16:17:49 -04:00
52 changed files with 1923 additions and 1082 deletions
+12
View File
@@ -41,6 +41,17 @@ async function configFetch(dispatch) {
});
}
async function userInfoFetch(dispatch) {
return fetchJson("userinfo").then((response) => {
const userinfo = { ...response.userinfo };
dispatch({
type: "userinfo load complete",
userinfo,
});
return userinfo;
});
}
function prefetchEmbeddings(annoMatrix) {
/*
prefetch requests for all embeddings
@@ -62,6 +73,7 @@ const doInitialDataLoad = () =>
configFetch(dispatch),
schemaFetch(dispatch),
userColorsFetchAndLoad(dispatch),
userInfoFetch(dispatch),
]);
const baseDataUrl = `${globals.API.prefix}${globals.API.version}`;
@@ -14,6 +14,7 @@ import {
idhash: state.config?.parameters?.["annotations-user-data-idhash"] ?? null,
annotations: state.annotations,
auth: state.config?.authentication,
userinfo: state.userinfo,
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
}))
class FilenameDialog extends React.Component {
@@ -91,13 +92,19 @@ class FilenameDialog extends React.Component {
};
render() {
const { writableCategoriesEnabled, annotations, idhash, auth } = this.props;
const {
writableCategoriesEnabled,
annotations,
idhash,
userinfo,
} = this.props;
const { filenameText } = this.state;
return writableCategoriesEnabled &&
annotations.promptForFilename &&
!annotations.dataCollectionNameIsReadOnly &&
!annotations.dataCollectionName &&
auth.is_authenticated ? (
userinfo.is_authenticated ? (
<Dialog
icon="tag"
title="Annotations Collection"
+35 -13
View File
@@ -1,6 +1,6 @@
// jshint esversion: 6
import React from "react";
import { Button } from "@blueprintjs/core";
import { AnchorButton, Tooltip, Position } from "@blueprintjs/core";
import { connect } from "react-redux";
import * as globals from "../../globals";
import Category from "./category";
@@ -15,6 +15,7 @@ import actions from "../../actions";
writableCategoriesEnabled: state.config?.parameters?.annotations ?? false,
schema: state.annoMatrix?.schema,
ontology: state.ontology,
userinfo: state.userinfo,
}))
class Categories extends React.Component {
constructor(props) {
@@ -127,7 +128,12 @@ class Categories extends React.Component {
newCategoryText,
expandedCats,
} = this.state;
const { writableCategoriesEnabled, schema, ontology } = this.props;
const {
writableCategoriesEnabled,
schema,
ontology,
userinfo,
} = this.props;
const ontologyEnabled = ontology?.enabled ?? false;
/* all names, sorted in display order. Will be rendered in this order */
const allCategoryNames = ControlsHelpers.selectableCategoryNames(
@@ -140,17 +146,6 @@ class Categories extends React.Component {
padding: globals.leftSidebarSectionPadding,
}}
>
{writableCategoriesEnabled ? (
<div>
<Button
data-testid="open-annotation-dialog"
onClick={this.handleEnableAnnoMode}
intent="primary"
>
Create new <strong>category</strong>
</Button>
</div>
) : null}
<AnnoDialog
isActive={createAnnoModeActive}
title="Create new category"
@@ -212,6 +207,33 @@ class Categories extends React.Component {
/>
) : null
)}
{writableCategoriesEnabled ? (
<Tooltip
content={
userinfo.is_authenticated
? "Create a new category"
: "You must be logged in to create new categorical fields"
}
position={Position.RIGHT}
boundary="viewport"
hoverOpenDelay={globals.tooltipHoverOpenDelay}
modifiers={{
preventOverflow: { enabled: false },
hide: { enabled: false },
}}
>
<AnchorButton
type="button"
data-testid="open-annotation-dialog"
onClick={this.handleEnableAnnoMode}
intent="primary"
disabled={!userinfo.is_authenticated}
>
Create new <strong>category</strong>
</AnchorButton>
</Tooltip>
) : null}
</div>
);
}
+4 -4
View File
@@ -4,7 +4,7 @@ import * as globals from "../../globals";
import styles from "./menubar.css";
const Auth = React.memo((props) => {
const { auth } = props;
const { auth, userinfo } = props;
if (!auth || (auth && !auth.requires_client_login)) return null;
@@ -19,10 +19,10 @@ const Auth = React.memo((props) => {
type="button"
data-testid="auth-button"
disabled={false}
icon={!auth.is_authenticated ? "log-in" : "log-out"}
href={!auth.is_authenticated ? auth.login : auth.logout}
icon={!userinfo.is_authenticated ? "log-in" : "log-out"}
href={!userinfo.is_authenticated ? auth.login : auth.logout}
>
{!auth.is_authenticated ? "Log In" : "Log Out"}
{!userinfo.is_authenticated ? "Log In" : "Log Out"}
</AnchorButton>
</Tooltip>
</div>
+3 -1
View File
@@ -42,6 +42,7 @@ import { getEmbSubsetView } from "../../util/stateManager/viewStackHelpers";
celllist2: state.differential.celllist2,
libraryVersions: state.config?.["library_versions"],
auth: state.config?.authentication,
userinfo: state.userinfo,
undoDisabled: state["@@undoable/past"].length === 0,
redoDisabled: state["@@undoable/future"].length === 0,
aboutLink: state.config?.links?.["about-dataset"],
@@ -221,6 +222,7 @@ class MenuBar extends React.PureComponent {
subsetResetPossible,
enableReembedding,
auth,
userinfo,
} = this.props;
const { pendingClipPercentiles } = this.state;
@@ -246,7 +248,7 @@ class MenuBar extends React.PureComponent {
zIndex: 3,
}}
>
<AuthButtons auth={auth} />
<AuthButtons auth={auth} userinfo={userinfo} />
<InformationMenu
libraryVersions={libraryVersions}
aboutLink={aboutLink}
@@ -528,8 +528,8 @@ class Scatterplot extends React.PureComponent {
return (
<ScatterplotAxis
minimized={minimized}
scatterplotYYaccessor={scatterplotXXaccessor}
scatterplotXXaccessor={scatterplotYYaccessor}
scatterplotYYaccessor={scatterplotYYaccessor}
scatterplotXXaccessor={scatterplotXXaccessor}
xScale={asyncProps.xScale}
yScale={asyncProps.yScale}
/>
+4
View File
@@ -26,6 +26,7 @@ const Annotations = (
categoryBeingEdited: null,
categoryAddingNewLabel: null,
labelEditable: { category: null, label: null },
promptForFilename: true,
},
action
) => {
@@ -37,10 +38,13 @@ const Annotations = (
action.config.parameters?.[
"annotations-data-collection-name-is-read-only"
] ?? false;
const promptForFilename =
action.config.parameters?.["user_annotation_collection_name_enabled"];
return {
...state,
dataCollectionNameIsReadOnly,
dataCollectionName,
promptForFilename,
};
}
+2
View File
@@ -4,6 +4,7 @@ import thunk from "redux-thunk";
import cascadeReducers from "./cascade";
import undoable from "./undoable";
import config from "./config";
import userinfo from "./userinfo";
import annoMatrix from "./annoMatrix";
import obsCrossfilter from "./obsCrossfilter";
import categoricalSelection from "./categoricalSelection";
@@ -43,6 +44,7 @@ const Reducer = undoable(
["pointDilation", pointDialation],
["reembedController", reembedController],
["autosave", autosave],
["userinfo", userinfo],
]),
[
"annoMatrix",
+27
View File
@@ -0,0 +1,27 @@
// jshint esversion: 6
const UserInfo = (state = {}, action) => {
switch (action.type) {
case "initial data load start":
return {
...state,
loading: true,
error: null,
};
case "userinfo load complete":
return {
...state,
loading: false,
error: null,
...action.userinfo,
};
case "initial data load error":
return {
...state,
error: action.error,
};
default:
return state;
}
};
export default UserInfo;
+20 -9
View File
@@ -94,15 +94,26 @@ export const createColorTable = memoize(_createColorTable);
export function loadUserColorConfig(userColors) {
const convertedUserColors = {};
Object.keys(userColors).forEach((category) => {
const [colors, scaleMap] = Object.keys(userColors[category]).reduce(
(acc, label, i) => {
const color = parseRGB(userColors[category][label]);
acc[0][label] = color;
acc[1][i] = d3.rgb(255 * color[0], 255 * color[1], 255 * color[2]);
return acc;
},
[{}, {}]
);
// We cannot iterate over keys without sorting
// because we handle categorical values in alphabetical order __ignoring case__
// while Object.keys() _usually_ is ordered alphabetically where all upper characters are less than lowercase (A, B, C, a, b, c)
const [colors, scaleMap] = Object.keys(userColors[category])
.sort((a, b) => {
a = a.toLowerCase();
b = b.toLowerCase();
if (a === b) return 0;
if (a > b) return 1;
return -1;
})
.reduce(
(acc, label, i) => {
const color = parseRGB(userColors[category][label]);
acc[0][label] = color;
acc[1][i] = d3.rgb(255 * color[0], 255 * color[1], 255 * color[2]);
return acc;
},
[{}, {}]
);
const scale = (i) => scaleMap[i];
convertedUserColors[category] = { colors, scale };
});