diff --git a/src/containers/CsvInput/index.tsx b/src/containers/CsvInput/index.tsx index fb265c3f..ae90d97f 100644 --- a/src/containers/CsvInput/index.tsx +++ b/src/containers/CsvInput/index.tsx @@ -3,44 +3,26 @@ import { Flex } from "antd"; import { RcFile } from "antd/es/upload"; import Dragger from "antd/es/upload/Dragger"; import React, { ReactElement } from "react"; -import { connect } from "react-redux"; +import { ActionCreator, 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, ReceiveImageDatasetAction } from "../../state/image-dataset/types"; -import metadataStateBranch from "../../state/metadata"; -import { ReceiveAvailableDatasetsAction } from "../../state/metadata/types"; -import selectionStateBranch from "../../state/selection"; -import { ChangeSelectionAction } from "../../state/selection/types"; - -type CsvInputProps = { - receiveImageDataset: (dataset: ImageDataset) => ReceiveImageDatasetAction; - receiveAvailableDatasets: (megasets: Megaset[]) => ReceiveAvailableDatasetsAction; - changeDataset: (id: string) => ChangeSelectionAction; -}; +import { State } from "../../state"; +import imageDatasetStateBranch from "../../state/image-dataset"; +import { LoadCsvDatasetAction } from "../../state/image-dataset/types"; type DispatchProps = { - receiveAvailableDatasets: (megasets: Megaset[]) => ReceiveAvailableDatasetsAction; - changeDataset: (id: string) => ChangeSelectionAction; - receiveImageDataset: (dataset: ImageDataset) => ReceiveImageDatasetAction; + loadCsvDataset: ActionCreator; }; +type CsvInputProps = DispatchProps; + /** * 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. */ function CsvInput(props: CsvInputProps): ReactElement { const action = async (file: RcFile): Promise => { - // TODO: handle loading via URL - const fileContents = await file.text(); - const dataset = new CsvRequest(fileContents); - props.receiveImageDataset(dataset); - - // CSV Request mocks up a single dataset - const megasets = await dataset.getAvailableDatasets(); - props.receiveAvailableDatasets(megasets); - props.changeDataset(DEFAULT_CSV_DATASET_KEY); - + const fileText = await file.text(); + props.loadCsvDataset(fileText); return Promise.resolve(""); }; @@ -55,10 +37,9 @@ function CsvInput(props: CsvInputProps): ReactElement { ); } -const dispatchToPropsMap = { - receiveAvailableDatasets: metadataStateBranch.actions.receiveAvailableDatasets, - changeDataset: selectionStateBranch.actions.changeDataset, - receiveImageDataset: imageDatasetBranch.actions.receiveImageDataset, + +const dispatchToPropsMap: DispatchProps = { + loadCsvDataset: imageDatasetStateBranch.actions.loadCsvDataset, }; -export default connect(undefined, dispatchToPropsMap)(CsvInput); +export default connect<{}, DispatchProps, {}, State>(null, dispatchToPropsMap)(CsvInput); diff --git a/src/state/configure-store.ts b/src/state/configure-store.ts index e8e80f1d..a58301ce 100755 --- a/src/state/configure-store.ts +++ b/src/state/configure-store.ts @@ -11,7 +11,7 @@ const reducers = { imageDataset: imageDataset.reducer, }; -const logics = [...metadata.logics, ...selection.logics]; +const logics = [...metadata.logics, ...selection.logics, ...imageDataset.logics]; const reduxLogicDependencies = { httpClient: axios, diff --git a/src/state/image-dataset/actions.ts b/src/state/image-dataset/actions.ts index f69d5ed8..0e0e0554 100644 --- a/src/state/image-dataset/actions.ts +++ b/src/state/image-dataset/actions.ts @@ -1,7 +1,14 @@ -import { ReceiveImageDatasetAction } from "./types"; -import { RECEIVE_IMAGE_DATASET } from "./constants"; +import { ChangeImageDatasetTypeAction } from "./types"; +import { CHANGE_IMAGE_DATASET_TYPE, LOAD_CSV_DATASET } from "./constants"; import { ImageDataset } from "./types"; -export function receiveImageDataset(payload: ImageDataset): ReceiveImageDatasetAction { - return { payload, type: RECEIVE_IMAGE_DATASET }; +export function changeImageDatasetType(payload: ImageDataset): ChangeImageDatasetTypeAction { + return { payload, type: CHANGE_IMAGE_DATASET_TYPE }; +} + +export function loadCsvDataset(payload: string) { + return { + payload, + type: LOAD_CSV_DATASET, + }; } diff --git a/src/state/image-dataset/constants.ts b/src/state/image-dataset/constants.ts index 51a7f23d..5601e982 100644 --- a/src/state/image-dataset/constants.ts +++ b/src/state/image-dataset/constants.ts @@ -1,3 +1,7 @@ import { makeConstant } from "../util"; -export const RECEIVE_IMAGE_DATASET = makeConstant("metadata", "receive-image-dataset"); +const makeImageDatasetConstant = (constant: string) => makeConstant("image-dataset", constant); + +export const LOAD_CSV_DATASET = makeImageDatasetConstant("load-csv-dataset"); + +export const CHANGE_IMAGE_DATASET_TYPE = makeImageDatasetConstant("change-image-dataset-type"); diff --git a/src/state/image-dataset/index.ts b/src/state/image-dataset/index.ts index bfa3aac9..f1528737 100644 --- a/src/state/image-dataset/index.ts +++ b/src/state/image-dataset/index.ts @@ -1,22 +1,12 @@ -import FirebaseRequest from "./firebase"; -import JsonRequest from "./json-dataset"; -import { ImageDataset } from "./types"; - -// by default will use Firebase for dataset, can be switched to JSON dataset using ENV -// variable -export function GetImageDatasetInstance(): ImageDataset { - return process.env.USE_JSON_DATASET ? new JsonRequest() : new FirebaseRequest(); -} - import * as actions from "./actions"; -// import logics from "./logics"; +import logics from "./logics"; import reducer from "./reducer"; import * as selectors from "./selectors"; import * as types from "./types"; export default { actions, - // logics, + logics, reducer, selectors, types, diff --git a/src/state/image-dataset/logics.ts b/src/state/image-dataset/logics.ts index e69de29b..5c6fb61b 100644 --- a/src/state/image-dataset/logics.ts +++ b/src/state/image-dataset/logics.ts @@ -0,0 +1,27 @@ +import { createLogic } from "redux-logic"; +import { ReduxLogicDeps } from ".."; +import { LOAD_CSV_DATASET } from "./constants"; +import CsvRequest, { DEFAULT_CSV_DATASET_KEY } from "./csv-dataset"; +import { changeImageDatasetType } from "./actions"; +import { receiveAvailableDatasets } from "../metadata/actions"; +import { changeDataset } from "../selection/actions"; + +/** + * Parses a CSV file and opens it as a new image dataset. + */ +const loadCsvDataset = createLogic({ + type: LOAD_CSV_DATASET, + async process(deps: ReduxLogicDeps, dispatch: any, done: any) { + const { action } = deps; + const fileContents = action.payload as string; + const dataset = new CsvRequest(fileContents); + dispatch(changeImageDatasetType(dataset)); + + const megasets = await dataset.getAvailableDatasets(); + dispatch(receiveAvailableDatasets(megasets)); + dispatch(changeDataset(DEFAULT_CSV_DATASET_KEY)); + done(); + }, +}); + +export default [loadCsvDataset]; diff --git a/src/state/image-dataset/reducer.ts b/src/state/image-dataset/reducer.ts index 57bb7da8..3c013968 100644 --- a/src/state/image-dataset/reducer.ts +++ b/src/state/image-dataset/reducer.ts @@ -1,22 +1,37 @@ import { TypeToDescriptionMap } from ".."; import { AnyAction } from "redux"; -import { RECEIVE_IMAGE_DATASET } from "./constants"; -import { ImageDatasetStateBranch, ReceiveAction } from "./types"; +import { CHANGE_IMAGE_DATASET_TYPE, LOAD_CSV_DATASET } from "./constants"; +import { + ImageDataset, + ImageDatasetStateBranch, + LoadCsvDatasetAction, + ReceiveImageDatasetAction, +} from "./types"; import { makeReducer } from "../util"; -import { GetImageDatasetInstance } from "."; +import FirebaseRequest from "./firebase"; +import JsonRequest from "./json-dataset"; + +// by default will use Firebase for dataset, can be switched to JSON dataset using ENV +// variable +function getImageDatasetInstance(): ImageDataset { + return process.env.USE_JSON_DATASET ? new JsonRequest() : new FirebaseRequest(); +} export const initialState: ImageDatasetStateBranch = { - imageDataset: GetImageDatasetInstance(), + 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, - }), + [CHANGE_IMAGE_DATASET_TYPE]: { + accepts: (action: AnyAction): action is ReceiveImageDatasetAction => + action.type === CHANGE_IMAGE_DATASET_TYPE, + perform: (state: ImageDatasetStateBranch, action: ReceiveImageDatasetAction) => { + console.log("Changing dataset type", action.payload); + return { + ...state, + imageDataset: action.payload, + }; + }, }, }; diff --git a/src/state/image-dataset/types.ts b/src/state/image-dataset/types.ts index 56e5e4b9..1c7891a6 100644 --- a/src/state/image-dataset/types.ts +++ b/src/state/image-dataset/types.ts @@ -9,8 +9,13 @@ export interface ImageDatasetStateBranch { imageDataset: ImageDataset; } -export interface ReceiveAction { - payload: { [key: string]: any }; +export interface ReceiveImageDatasetAction { + payload: ImageDataset; + type: string; +} + +export interface LoadCsvDatasetAction { + payload: string; // CSV contents type: string; } @@ -75,7 +80,7 @@ export interface ImageDataset { getFileInfoByArrayOfCellIds(ids: string[]): Promise<(FileInfo | undefined)[]>; } // ACTIONS -export interface ReceiveImageDatasetAction { +export interface ChangeImageDatasetTypeAction { payload: ImageDataset; type: string; } diff --git a/src/state/metadata/actions.ts b/src/state/metadata/actions.ts index 9bd757c6..96a0c510 100755 --- a/src/state/metadata/actions.ts +++ b/src/state/metadata/actions.ts @@ -34,7 +34,6 @@ import { SetLoadingAction, SetSmallScreenWarningAction, } from "./types"; -import { ReceiveImageDatasetAction } from "../image-dataset/types"; export function requestAvailableDatasets() { return { type: REQUEST_AVAILABLE_DATASETS };