diff --git a/src/components/LandingPage/index.tsx b/src/components/LandingPage/index.tsx index 34dcfbc8..5fa48bf7 100644 --- a/src/components/LandingPage/index.tsx +++ b/src/components/LandingPage/index.tsx @@ -1,11 +1,11 @@ +import { Col, Descriptions, Layout, Row } from "antd"; import React from "react"; -import { Row, Col, Layout, Descriptions } from "antd"; const { Content } = Layout; import MegasetCard from "../../components/MegasetCard"; +import CsvInput from "../../containers/CsvInput"; import { Megaset } from "../../state/image-dataset/types"; import downloadData, { DownloadInfo } from "./download-data"; -import CsvInput from "../CsvInput"; import styles from "./style.css"; diff --git a/src/containers/Cfe/test/selectors.test.ts b/src/containers/Cfe/test/selectors.test.ts index a79ae038..993d792f 100644 --- a/src/containers/Cfe/test/selectors.test.ts +++ b/src/containers/Cfe/test/selectors.test.ts @@ -32,9 +32,7 @@ const fileInfo: FileInfo[] = [ ]; const stateWithSelections: State = { - metadata: { - ...mockState.metadata, - }, + ...mockState, selection: { ...mockState.selection, cellSelectedFor3D: "1", diff --git a/src/components/CsvInput/index.tsx b/src/containers/CsvInput/index.tsx similarity index 79% rename from src/components/CsvInput/index.tsx rename to src/containers/CsvInput/index.tsx index 60f8dce1..fb265c3f 100644 --- a/src/components/CsvInput/index.tsx +++ b/src/containers/CsvInput/index.tsx @@ -2,19 +2,16 @@ import { PlusOutlined } from "@ant-design/icons"; import { Flex } from "antd"; import { RcFile } from "antd/es/upload"; import Dragger from "antd/es/upload/Dragger"; -import { connect } from "react-redux"; import React, { ReactElement } from "react"; +import { connect } from "react-redux"; +import imageDatasetBranch from "../../state/image-dataset"; import CsvRequest, { DEFAULT_CSV_DATASET_KEY } from "../../state/image-dataset/csv-dataset"; -import { ImageDataset, Megaset } from "../../state/image-dataset/types"; +import { ImageDataset, Megaset, ReceiveImageDatasetAction } from "../../state/image-dataset/types"; import metadataStateBranch from "../../state/metadata"; -import { - ReceiveAvailableDatasetsAction, - ReceiveImageDatasetAction, -} from "../../state/metadata/types"; +import { ReceiveAvailableDatasetsAction } from "../../state/metadata/types"; import selectionStateBranch from "../../state/selection"; import { ChangeSelectionAction } from "../../state/selection/types"; -import { receiveImageDataset } from "../../state/metadata/actions"; type CsvInputProps = { receiveImageDataset: (dataset: ImageDataset) => ReceiveImageDatasetAction; @@ -22,6 +19,12 @@ type CsvInputProps = { changeDataset: (id: string) => ChangeSelectionAction; }; +type DispatchProps = { + receiveAvailableDatasets: (megasets: Megaset[]) => ReceiveAvailableDatasetsAction; + changeDataset: (id: string) => ChangeSelectionAction; + receiveImageDataset: (dataset: ImageDataset) => ReceiveImageDatasetAction; +}; + /** * An input area for CSV files. When CSV data is provided, replaces the current image dataset * with a new `CsvRequest` image dataset and triggers the loading of the CSV data. @@ -55,7 +58,7 @@ function CsvInput(props: CsvInputProps): ReactElement { const dispatchToPropsMap = { receiveAvailableDatasets: metadataStateBranch.actions.receiveAvailableDatasets, changeDataset: selectionStateBranch.actions.changeDataset, - receiveImageDataset: metadataStateBranch.actions.receiveImageDataset, + receiveImageDataset: imageDatasetBranch.actions.receiveImageDataset, }; export default connect(undefined, dispatchToPropsMap)(CsvInput); diff --git a/src/state/configure-store.ts b/src/state/configure-store.ts index a33d5c27..e8e80f1d 100755 --- a/src/state/configure-store.ts +++ b/src/state/configure-store.ts @@ -3,11 +3,12 @@ import { merge } from "lodash"; import { applyMiddleware, combineReducers, createStore } from "redux"; import { createLogicMiddleware } from "redux-logic"; -import { enableBatching, initialState, metadata, selection, State } from "./"; +import { enableBatching, initialState, imageDataset, metadata, selection, State } from "./"; const reducers = { metadata: metadata.reducer, selection: selection.reducer, + imageDataset: imageDataset.reducer, }; const logics = [...metadata.logics, ...selection.logics]; diff --git a/src/state/image-dataset/actions.ts b/src/state/image-dataset/actions.ts new file mode 100644 index 00000000..f69d5ed8 --- /dev/null +++ b/src/state/image-dataset/actions.ts @@ -0,0 +1,7 @@ +import { ReceiveImageDatasetAction } from "./types"; +import { RECEIVE_IMAGE_DATASET } from "./constants"; +import { ImageDataset } from "./types"; + +export function receiveImageDataset(payload: ImageDataset): ReceiveImageDatasetAction { + return { payload, type: RECEIVE_IMAGE_DATASET }; +} diff --git a/src/state/image-dataset/constants.ts b/src/state/image-dataset/constants.ts new file mode 100644 index 00000000..51a7f23d --- /dev/null +++ b/src/state/image-dataset/constants.ts @@ -0,0 +1,3 @@ +import { makeConstant } from "../util"; + +export const RECEIVE_IMAGE_DATASET = makeConstant("metadata", "receive-image-dataset"); diff --git a/src/state/image-dataset/index.ts b/src/state/image-dataset/index.ts index f7579ed2..bfa3aac9 100644 --- a/src/state/image-dataset/index.ts +++ b/src/state/image-dataset/index.ts @@ -4,6 +4,20 @@ import { ImageDataset } from "./types"; // by default will use Firebase for dataset, can be switched to JSON dataset using ENV // variable -export default function GetImageDatasetInstance(): ImageDataset { +export function GetImageDatasetInstance(): ImageDataset { return process.env.USE_JSON_DATASET ? new JsonRequest() : new FirebaseRequest(); } + +import * as actions from "./actions"; +// import logics from "./logics"; +import reducer from "./reducer"; +import * as selectors from "./selectors"; +import * as types from "./types"; + +export default { + actions, + // logics, + reducer, + selectors, + types, +}; diff --git a/src/state/image-dataset/logics.ts b/src/state/image-dataset/logics.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/state/image-dataset/reducer.ts b/src/state/image-dataset/reducer.ts new file mode 100644 index 00000000..57bb7da8 --- /dev/null +++ b/src/state/image-dataset/reducer.ts @@ -0,0 +1,23 @@ +import { TypeToDescriptionMap } from ".."; +import { AnyAction } from "redux"; +import { RECEIVE_IMAGE_DATASET } from "./constants"; +import { ImageDatasetStateBranch, ReceiveAction } from "./types"; +import { makeReducer } from "../util"; +import { GetImageDatasetInstance } from "."; + +export const initialState: ImageDatasetStateBranch = { + imageDataset: GetImageDatasetInstance(), +}; + +const actionToConfigMap: TypeToDescriptionMap = { + [RECEIVE_IMAGE_DATASET]: { + accepts: (action: AnyAction): action is ReceiveAction => + action.type === RECEIVE_IMAGE_DATASET, + perform: (state: ImageDatasetStateBranch, action: ReceiveAction) => ({ + ...state, + imageDataset: action.payload, + }), + }, +}; + +export default makeReducer(actionToConfigMap, initialState); diff --git a/src/state/image-dataset/selectors.ts b/src/state/image-dataset/selectors.ts new file mode 100644 index 00000000..078c52c1 --- /dev/null +++ b/src/state/image-dataset/selectors.ts @@ -0,0 +1,4 @@ +import { State } from ".."; +import { ImageDataset } from "./types"; + +export const getImageDataset = (state: State): ImageDataset => state.imageDataset.imageDataset; diff --git a/src/state/image-dataset/types.ts b/src/state/image-dataset/types.ts index 2f587b4d..56e5e4b9 100644 --- a/src/state/image-dataset/types.ts +++ b/src/state/image-dataset/types.ts @@ -5,6 +5,15 @@ import { ViewerChannelSettings } from "@aics/web-3d-viewer/type-declarations"; import { DataForPlot, FileInfo, MeasuredFeatureDef } from "../metadata/types"; import { Album } from "../types"; +export interface ImageDatasetStateBranch { + imageDataset: ImageDataset; +} + +export interface ReceiveAction { + payload: { [key: string]: any }; + type: string; +} + export interface InitialDatasetSelections { defaultXAxis: string; defaultYAxis: string; @@ -64,4 +73,9 @@ export interface ImageDataset { getMeasuredFeatureDefs(): Promise; getFileInfoByCellId(id: string): Promise; getFileInfoByArrayOfCellIds(ids: string[]): Promise<(FileInfo | undefined)[]>; +} // ACTIONS + +export interface ReceiveImageDatasetAction { + payload: ImageDataset; + type: string; } diff --git a/src/state/index.ts b/src/state/index.ts index 99b22f20..658fc315 100755 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -1,7 +1,9 @@ +import { initialState as imageDatasetInitialState } from "./image-dataset/reducer"; import { initialState as metadataInitialState } from "./metadata/reducer"; import { initialState as selectionInitialState } from "./selection/reducer"; import { State } from "./types"; +export { default as imageDataset } from "./image-dataset"; export { default as metadata } from "./metadata"; export { default as selection } from "./selection"; @@ -14,4 +16,5 @@ export * from "./types"; export const initialState: State = Object.freeze({ metadata: metadataInitialState, selection: selectionInitialState, + imageDataset: imageDatasetInitialState, }); diff --git a/src/state/metadata/actions.ts b/src/state/metadata/actions.ts index 922aa83e..9bd757c6 100755 --- a/src/state/metadata/actions.ts +++ b/src/state/metadata/actions.ts @@ -19,7 +19,6 @@ import { SET_IS_LOADING, SET_LOADING_TEXT, SET_SHOW_SMALL_SCREEN_WARNING, - RECEIVE_IMAGE_DATASET, } from "./constants"; import { DataForPlot, @@ -29,17 +28,13 @@ import { ReceiveAlbumDataAction, ReceiveAvailableDatasetsAction, ReceiveCellFileInfoAction, - ReceiveImageDatasetAction, ReceiveMeasuredFeaturesAction, ReceiveViewerChannelSettingsAction, RequestAction, SetLoadingAction, SetSmallScreenWarningAction, } from "./types"; - -export function receiveImageDataset(payload: ImageDataset): ReceiveImageDatasetAction { - return { payload, type: RECEIVE_IMAGE_DATASET }; -} +import { ReceiveImageDatasetAction } from "../image-dataset/types"; export function requestAvailableDatasets() { return { type: REQUEST_AVAILABLE_DATASETS }; diff --git a/src/state/metadata/constants.ts b/src/state/metadata/constants.ts index c0b5673f..14d119ba 100755 --- a/src/state/metadata/constants.ts +++ b/src/state/metadata/constants.ts @@ -1,6 +1,5 @@ import { makeConstant } from "../util"; -export const RECEIVE_IMAGE_DATASET = makeConstant("metadata", "receive-image-dataset"); export const RECEIVE_DATA_FOR_PLOT = makeConstant("metadata", "receive"); export const REQUEST_FEATURE_DATA = makeConstant("metadata", "request"); export const REQUEST_ALBUM_DATA = makeConstant("metadata", "request-album-data"); diff --git a/src/state/metadata/logics.ts b/src/state/metadata/logics.ts index 936fd0c1..943943c8 100755 --- a/src/state/metadata/logics.ts +++ b/src/state/metadata/logics.ts @@ -37,11 +37,13 @@ import { getSelectedIdsFromUrl, } from "../selection/selectors"; import { ViewerChannelSettings } from "@aics/web-3d-viewer/type-declarations"; +import { getImageDataset } from "../image-dataset/selectors"; const requestAvailableDatasets = createLogic({ process(deps: ReduxLogicDeps, dispatch: any, done: any) { const { getState } = deps; - const imageDataSet = getState().metadata.imageDataset; + const state = getState(); + const imageDataSet = getImageDataset(state); return imageDataSet .getAvailableDatasets() .then((data: Megaset[]) => dispatch(receiveAvailableDatasets(data))) @@ -85,7 +87,8 @@ export const findVisibleDataPoint = ( const requestFeatureDataLogic = createLogic({ async process(deps: ReduxLogicDeps, dispatch: any, done: any) { const { getState } = deps; - const imageDataSet = getState().metadata.imageDataset; + const state = getState(); + const imageDataSet = getImageDataset(state); const showSmallScreenWarning = getShowSmallScreenWarning(getState()); if (showSmallScreenWarning) return; @@ -112,7 +115,6 @@ const requestFeatureDataLogic = createLogic({ } // select first cell on both plot and load in 3D to make it clear what the user can do // BUT only if those selections have not been previously made (e.g., passed through URL params) - const state = getState(); const selectedCellIdsFromUrls = getSelectedIdsFromUrl(state); let selectedCellIndex = 0; if (selectedCellIdsFromUrls.length) { @@ -155,7 +157,9 @@ const requestFeatureDataLogic = createLogic({ const requestAlbumData = createLogic({ process(deps: ReduxLogicDeps, dispatch: any, done: any) { const { getState } = deps; - const imageDataSet = getState().metadata.imageDataset; + const state = getState(); + const imageDataSet = getImageDataset(state); + dispatch(setLoadingText("Loading album data...")); return imageDataSet .getAlbumData() @@ -174,7 +178,9 @@ const requestAlbumData = createLogic({ const requestViewerChannelSettings = createLogic({ process(deps: ReduxLogicDeps, dispatch: any, done: any) { const { getState } = deps; - const imageDataSet = getState().metadata.imageDataset; + const state = getState(); + const imageDataSet = getImageDataset(state); + return imageDataSet .getViewerChannelSettings() .then((data: ViewerChannelSettings) => { diff --git a/src/state/metadata/reducer.ts b/src/state/metadata/reducer.ts index e3ef7fbe..6cd657a5 100755 --- a/src/state/metadata/reducer.ts +++ b/src/state/metadata/reducer.ts @@ -1,7 +1,6 @@ import { ViewerChannelSettings } from "@aics/web-3d-viewer/type-declarations"; import { AnyAction } from "redux"; import { Megaset } from "../image-dataset/types"; -import GetImageDatasetInstance from "../image-dataset"; import { ReceiveCellFileInfoAction } from "../selection/types"; import { TypeToDescriptionMap } from "../types"; @@ -18,7 +17,6 @@ import { SET_IS_LOADING, SET_LOADING_TEXT, SET_SHOW_SMALL_SCREEN_WARNING, - RECEIVE_IMAGE_DATASET, } from "./constants"; import { MetadataStateBranch, @@ -36,7 +34,6 @@ import { } from "./types"; export const initialState: MetadataStateBranch = { - imageDataset: GetImageDatasetInstance(), albums: [], cellFileInfo: [] as FileInfo[], isLoading: true, @@ -53,14 +50,6 @@ export const initialState: MetadataStateBranch = { }; const actionToConfigMap: TypeToDescriptionMap = { - [RECEIVE_IMAGE_DATASET]: { - accepts: (action: AnyAction): action is ReceiveAction => - action.type === RECEIVE_IMAGE_DATASET, - perform: (state: MetadataStateBranch, action: ReceiveAction) => ({ - ...state, - imageDataset: action.payload, - }), - }, [RECEIVE_DATA_FOR_PLOT]: { accepts: (action: AnyAction): action is ReceiveAction => action.type === RECEIVE_DATA_FOR_PLOT, diff --git a/src/state/metadata/types.ts b/src/state/metadata/types.ts index 3138745a..5d9ddf47 100755 --- a/src/state/metadata/types.ts +++ b/src/state/metadata/types.ts @@ -8,12 +8,11 @@ import { TRANSFORM, VOLUME_VIEWER_PATH, } from "../../constants"; -import { ImageDataset, Megaset } from "../image-dataset/types"; +import { Megaset } from "../image-dataset/types"; import { Album } from "../types"; import { ViewerChannelSettings } from "@aics/web-3d-viewer/type-declarations"; export interface MetadataStateBranch { - imageDataset: ImageDataset; albums: Album[]; cellFileInfo: FileInfo[]; isLoading: boolean; @@ -97,13 +96,6 @@ export interface DataForPlot { labels: PerCellLabels; } -// ACTIONS - -export interface ReceiveImageDatasetAction { - payload: ImageDataset; - type: string; -} - export interface ReceiveAction { payload: { [key: string]: any }; type: string; diff --git a/src/state/selection/logics.ts b/src/state/selection/logics.ts index ef0a4225..2af931d0 100755 --- a/src/state/selection/logics.ts +++ b/src/state/selection/logics.ts @@ -23,6 +23,7 @@ import { COLOR_BY_SELECTOR, X_AXIS_ID, Y_AXIS_ID } from "../../constants"; import { changeAxis, changeGroupByCategory } from "./actions"; import { FileInfo } from "../metadata/types"; import { DatasetMetaData } from "../image-dataset/types"; +import { getImageDataset } from "../image-dataset/selectors"; const syncStateWithUrl = createLogic({ type: SYNC_STATE_WITH_URL, @@ -43,7 +44,9 @@ const changeDatasetLogic = createLogic({ type: CHANGE_DATASET, async process(deps: ReduxLogicDeps, dispatch: any, done: any) { const { action, getState } = deps; - const imageDataSet = getState().metadata.imageDataset; + const state = getState(); + const imageDataSet = getImageDataset(state); + if (!action.payload) { return dispatch({ type: SET_DATASET, @@ -102,7 +105,8 @@ const changeDatasetLogic = createLogic({ const requestCellFileInfoForSelectedPoint = createLogic({ process(deps: ReduxLogicDeps) { const { action, getState } = deps; - const imageDataSet = getState().metadata.imageDataset; + const state = getState(); + const imageDataSet = getImageDataset(state); return imageDataSet .getFileInfoByCellId(action.payload.id.toString()) .then((data?: FileInfo) => { @@ -140,7 +144,7 @@ const requestCellFileInfoForSelectedArrayOfPoints = createLogic({ process(deps: ReduxLogicDeps) { const { action, getState } = deps; const state = getState(); - const imageDataSet = state.metadata.imageDataset; + const imageDataSet = getImageDataset(state); const plotData = getPerCellDataForPlot(state); const indices = getIndicesForCellIds(action.payload, plotData.labels.cellIds); @@ -171,7 +175,8 @@ const requestCellFileInfoForSelectedArrayOfPoints = createLogic({ const selectAlbum = createLogic({ process(deps: ReduxLogicDeps) { const { action, getState } = deps; - const imageDataSet = getState().metadata.imageDataset; + const state = getState(); + const imageDataSet = getImageDataset(state); if (!imageDataSet.getFileInfoByArrayOfCellIds) { return Promise.resolve({}); } diff --git a/src/state/test/configure-mock-store.ts b/src/state/test/configure-mock-store.ts index 6fa26f16..e8acbc16 100755 --- a/src/state/test/configure-mock-store.ts +++ b/src/state/test/configure-mock-store.ts @@ -8,6 +8,7 @@ import { SinonStub } from "sinon"; import { enableBatching, + imageDataset, initialState, metadata, selection, @@ -23,6 +24,7 @@ export interface ReduxLogicDependencies { const reducers = { metadata: metadata.reducer, selection: selection.reducer, + imageDataset: imageDataset.reducer, }; const logics = [ diff --git a/src/state/test/mocks.ts b/src/state/test/mocks.ts index 81cfb2d8..91eec6ea 100644 --- a/src/state/test/mocks.ts +++ b/src/state/test/mocks.ts @@ -1,17 +1,18 @@ import { ViewerChannelSettings } from "@aics/web-3d-viewer/type-declarations"; import { + ARRAY_OF_CELL_IDS_KEY, CELL_ID_KEY, FOV_ID_KEY, - ARRAY_OF_CELL_IDS_KEY, FOV_THUMBNAIL_PATH, FOV_VOLUME_VIEWER_PATH, THUMBNAIL_PATH, VOLUME_VIEWER_PATH, } from "../../constants"; +import { initialState as initialImageDataset } from "../image-dataset/reducer"; import { initialState as initialMetaData } from "../metadata/reducer"; -import { initialState as initialSelectionState } from "../selection/reducer"; import { DataForPlot, FileInfo, MeasuredFeatureDef } from "../metadata/types"; import { INITIAL_COLORS } from "../selection/constants"; +import { initialState as initialSelectionState } from "../selection/reducer"; export const selectedCellFileInfo: FileInfo[] = [ { @@ -159,6 +160,7 @@ const displayableGroups: string[] = ["Paxillin", "Alpha-actinin-1"]; const INITIAL_COLOR_AND_GROUP_BY = "cell-line"; export const mockState = { + imageDataset: initialImageDataset, metadata: { ...initialMetaData, cellFileInfo: fileInfo, diff --git a/src/state/types.ts b/src/state/types.ts index 0c8720a3..13f192b1 100755 --- a/src/state/types.ts +++ b/src/state/types.ts @@ -4,6 +4,7 @@ import { AnyAction } from "redux"; import { MetadataStateBranch } from "./metadata/types"; import { LassoOrBoxSelectPointData, SelectionStateBranch } from "./selection/types"; +import { ImageDatasetStateBranch } from "./image-dataset/types"; export type NumberOrString = number | string; @@ -30,6 +31,7 @@ export type ReduxLogicNextCb = (action: AnyAction) => void; export interface State { metadata: MetadataStateBranch; selection: SelectionStateBranch; + imageDataset: ImageDatasetStateBranch; } export interface TypeToDescriptionMap {