From 5c070a9484719acf88145da134cdee3fb545a8b2 Mon Sep 17 00:00:00 2001 From: Joe Heffernan Date: Mon, 18 Nov 2024 12:52:22 -0800 Subject: [PATCH] Feature/selector org (#606) * add compoundSelectors to redux * add ColorSettings enum and use container specific selector in ModelPanel * add currentColorSettings to selection branch state * rename ColorSetting from plural * fix typo in selection branch spread in getCurrentUIData test * add explanatory comment to compoundSelectors --- src/containers/ModelPanel/index.tsx | 6 +- src/containers/ModelPanel/selectors.test.ts | 75 ++++++++++++++++-- src/containers/ModelPanel/selectors.ts | 26 +++++- .../compoundSelectors.test.ts | 79 +++++++++++++++++++ src/state/compoundSelectors/index.ts | 34 ++++++++ src/state/selection/reducer.ts | 2 + src/state/selection/selectors/basic.ts | 2 + src/state/selection/types.ts | 5 ++ src/state/trajectory/selectors/index.ts | 26 +----- .../trajectory/selectors/selectors.test.ts | 67 +--------------- 10 files changed, 220 insertions(+), 102 deletions(-) create mode 100644 src/state/compoundSelectors/compoundSelectors.test.ts create mode 100644 src/state/compoundSelectors/index.ts diff --git a/src/containers/ModelPanel/index.tsx b/src/containers/ModelPanel/index.tsx index 66b3f57e1..b21a29678 100644 --- a/src/containers/ModelPanel/index.tsx +++ b/src/containers/ModelPanel/index.tsx @@ -10,10 +10,7 @@ import { requestTrajectory, changeToNetworkedFile, } from "../../state/trajectory/actions"; -import { - getUiDisplayDataTree, - getIsNetworkedFile, -} from "../../state/trajectory/selectors"; +import { getIsNetworkedFile } from "../../state/trajectory/selectors"; import { AgentRenderingCheckboxMap, ChangeAgentsRenderingStateAction, @@ -44,6 +41,7 @@ import { getSelectAllVisibilityMap, getSelectNoneVisibilityMap, getIsSharedCheckboxIndeterminate, + getUiDisplayDataTree, } from "./selectors"; import styles from "./style.css"; diff --git a/src/containers/ModelPanel/selectors.test.ts b/src/containers/ModelPanel/selectors.test.ts index 8f2acb2cf..50146f211 100644 --- a/src/containers/ModelPanel/selectors.test.ts +++ b/src/containers/ModelPanel/selectors.test.ts @@ -1,7 +1,9 @@ +import { initialState, State } from "../../state"; import { getSelectAllVisibilityMap, getSelectNoneVisibilityMap, getIsSharedCheckboxIndeterminate, + getUiDisplayDataTree, } from "./selectors"; const mockUiDisplayData = [ @@ -50,9 +52,8 @@ const mockUiDisplayData = [ describe("ModelPanel selectors", () => { describe("getSelectAllVisibilityMap", () => { it("Returns an agent visibility map with all possible states", () => { - const result = getSelectAllVisibilityMap.resultFunc( - mockUiDisplayData - ); + const result = + getSelectAllVisibilityMap.resultFunc(mockUiDisplayData); const expected = { agentWithChildren1: ["", "state1"], agentWithChildren2: ["", "state1"], @@ -64,9 +65,8 @@ describe("ModelPanel selectors", () => { describe("getSelectNoneVisibilityMap", () => { it("Returns an agent visibility map with none of the possible states", () => { - const result = getSelectNoneVisibilityMap.resultFunc( - mockUiDisplayData - ); + const result = + getSelectNoneVisibilityMap.resultFunc(mockUiDisplayData); const expected = { agentWithChildren1: [], agentWithChildren2: [], @@ -164,4 +164,67 @@ describe("ModelPanel selectors", () => { expect(result).toBe(true); }); }); + describe("getUiDisplayDataTree", () => { + it("returns an empty array if ui display data is empty", () => { + expect(getUiDisplayDataTree(initialState)).toStrictEqual([]); + }); + it("correctly maps agent display info to an array of display data", () => { + const state: State = { + ...initialState, + trajectory: { + ...initialState.trajectory, + defaultUIData: [ + { + name: "agent1", + displayStates: [], + color: "#bbbbbb", + }, + { + name: "agent2", + color: "#aaaaaa", + displayStates: [ + { + name: "state1", + id: "state1_id", + color: "#000000", + }, + { + name: "state2", + id: "state2_id", + color: "#000000", + }, + ], + }, + ], + }, + }; + + const expected = [ + { + title: "agent1", + key: "agent1", + children: [], + color: "#bbbbbb", + }, + { + title: "agent2", + key: "agent2", + color: "#aaaaaa", + children: [ + { + color: "#000000", + label: "state1", + value: "state1_id", + }, + { + color: "#000000", + label: "state2", + value: "state2_id", + }, + ], + }, + ]; + expect(getUiDisplayDataTree(state)).toStrictEqual(expected); + }); + }); }); diff --git a/src/containers/ModelPanel/selectors.ts b/src/containers/ModelPanel/selectors.ts index dd8962207..da2017685 100644 --- a/src/containers/ModelPanel/selectors.ts +++ b/src/containers/ModelPanel/selectors.ts @@ -2,9 +2,33 @@ import { createSelector } from "reselect"; import { isEmpty } from "lodash"; import { AgentDisplayNode } from "../../components/AgentTree"; -import { getUiDisplayDataTree } from "../../state/trajectory/selectors"; import { getAgentVisibilityMap } from "../../state/selection/selectors"; import { AgentRenderingCheckboxMap } from "../../state/selection/types"; +import { getCurrentUIData } from "../../state/compoundSelectors"; +import { UIDisplayData } from "@aics/simularium-viewer"; + +export const getUiDisplayDataTree = createSelector( + [getCurrentUIData], + (uiDisplayData: UIDisplayData) => { + if (!uiDisplayData.length) { + return []; + } + return uiDisplayData.map((agent) => ({ + title: agent.name, + key: agent.name, + color: agent.color, + children: agent.displayStates.length + ? [ + ...agent.displayStates.map((state) => ({ + label: state.name, + value: state.id, + color: state.color, + })), + ] + : [], + })); + } +); // Returns an agent visibility map that indicates all states should be visible export const getSelectAllVisibilityMap = createSelector( diff --git a/src/state/compoundSelectors/compoundSelectors.test.ts b/src/state/compoundSelectors/compoundSelectors.test.ts new file mode 100644 index 000000000..008ca7ff4 --- /dev/null +++ b/src/state/compoundSelectors/compoundSelectors.test.ts @@ -0,0 +1,79 @@ +import { getCurrentUIData } from "."; +import { initialState } from ".."; +import { ColorSetting } from "../selection/types"; + +describe("getCurrentUIData", () => { + it("returns empty array if default UI data has not been entered yet", () => { + expect(getCurrentUIData(initialState)).toEqual([]); + 1; + }); + it("returns selectedUIDisplayData if colorSetting is equal to ColorSetting.UserSelected", () => { + expect( + getCurrentUIData({ + ...initialState, + trajectory: { + ...initialState.trajectory, + defaultUIData: [ + { + name: "agent1", + displayStates: [], + color: "#bbbbbb", + }, + ], + }, + selection: { + ...initialState.selection, + currentColorSetting: ColorSetting.UserSelected, + selectedUIDisplayData: [ + { + name: "agent1", + displayStates: [], + color: "#000", + }, + ], + }, + }) + ).toEqual([ + { + name: "agent1", + displayStates: [], + color: "#000", + }, + ]); + }); + + it("returns defaultUIData if colorSetting is euqal to ColorSetting.Default", () => { + expect( + getCurrentUIData({ + ...initialState, + trajectory: { + ...initialState.trajectory, + defaultUIData: [ + { + name: "agent1", + displayStates: [], + color: "#bbbbbb", + }, + ], + }, + selection: { + ...initialState.selection, + currentColorSetting: ColorSetting.Default, + selectedUIDisplayData: [ + { + name: "agent1", + displayStates: [], + color: "#000", + }, + ], + }, + }) + ).toEqual([ + { + name: "agent1", + displayStates: [], + color: "#bbbbbb", + }, + ]); + }); +}); diff --git a/src/state/compoundSelectors/index.ts b/src/state/compoundSelectors/index.ts new file mode 100644 index 000000000..0f9f50d73 --- /dev/null +++ b/src/state/compoundSelectors/index.ts @@ -0,0 +1,34 @@ +import { createSelector } from "reselect"; +import { UIDisplayData } from "@aics/simularium-viewer"; + +import { getDefaultUIDisplayData } from "../trajectory/selectors"; +import { + getCurrentColorSetting, + getSelectedUIDisplayData, +} from "../selection/selectors"; +import { ColorSetting } from "../selection/types"; + +/** + * compoundSelectors are selectors that consume state from multiple branches + * of state, so don't belong in a particular branch's selectors file, + * and are consumed by multiple containers, and so don't belong in a particular + * container's selectors file. + */ + +export const getCurrentUIData = createSelector( + [getCurrentColorSetting, getSelectedUIDisplayData, getDefaultUIDisplayData], + ( + colorSetting: ColorSetting, + sessionData: UIDisplayData, + defaultData: UIDisplayData + ) => { + const fileHasBeenParsed = defaultData.length > 0; + if (!fileHasBeenParsed) { + return []; + } + if (colorSetting === ColorSetting.UserSelected) { + return sessionData; + } + return defaultData; + } +); diff --git a/src/state/selection/reducer.ts b/src/state/selection/reducer.ts index d0b064720..8b502b074 100644 --- a/src/state/selection/reducer.ts +++ b/src/state/selection/reducer.ts @@ -26,6 +26,7 @@ import { SetRecentColorsAction, SetSelectedAgentMetadataAction, SetSelectedUIDisplayDataAction, + ColorSetting, } from "./types"; export const initialState = { @@ -36,6 +37,7 @@ export const initialState = { recentColors: [], selectedAgentMetadata: {}, selectedUIDisplayData: [], + currentColorSetting: ColorSetting.Default, }; const actionToConfigMap: TypeToDescriptionMap = { diff --git a/src/state/selection/selectors/basic.ts b/src/state/selection/selectors/basic.ts index e9b91488b..671aab372 100644 --- a/src/state/selection/selectors/basic.ts +++ b/src/state/selection/selectors/basic.ts @@ -13,3 +13,5 @@ export const getSelectedAgentMetadata = (state: State) => state.selection.selectedAgentMetadata; export const getSelectedUIDisplayData = (state: State) => state.selection.selectedUIDisplayData; +export const getCurrentColorSetting = (state: State) => + state.selection.currentColorSetting; diff --git a/src/state/selection/types.ts b/src/state/selection/types.ts index db4273846..dc1239bc4 100644 --- a/src/state/selection/types.ts +++ b/src/state/selection/types.ts @@ -87,3 +87,8 @@ export interface SetSelectedUIDisplayDataAction { payload: UIDisplayData; type: string; } + +export enum ColorSetting { + UserSelected = "userSelected", + Default = "default", +} diff --git a/src/state/trajectory/selectors/index.ts b/src/state/trajectory/selectors/index.ts index ecd074b2d..ea882f4ff 100644 --- a/src/state/trajectory/selectors/index.ts +++ b/src/state/trajectory/selectors/index.ts @@ -1,12 +1,11 @@ import { createSelector } from "reselect"; -import { UIDisplayData } from "@aics/simularium-viewer"; import { isNetworkSimFileInterface, LocalSimFile, NetworkedSimFile, } from "../types"; -import { getSimulariumFile, getDefaultUIDisplayData } from "./basic"; +import { getSimulariumFile } from "./basic"; export const getIsNetworkedFile = createSelector( [getSimulariumFile], @@ -18,27 +17,4 @@ export const getIsNetworkedFile = createSelector( } ); -export const getUiDisplayDataTree = createSelector( - [getDefaultUIDisplayData], - (uiDisplayData: UIDisplayData) => { - if (!uiDisplayData.length) { - return []; - } - return uiDisplayData.map((agent) => ({ - title: agent.name, - key: agent.name, - color: agent.color, - children: agent.displayStates.length - ? [ - ...agent.displayStates.map((state) => ({ - label: state.name, - value: state.id, - color: state.color, - })), - ] - : [], - })); - } -); - export * from "./basic"; diff --git a/src/state/trajectory/selectors/selectors.test.ts b/src/state/trajectory/selectors/selectors.test.ts index f236abe74..0fb773786 100644 --- a/src/state/trajectory/selectors/selectors.test.ts +++ b/src/state/trajectory/selectors/selectors.test.ts @@ -1,7 +1,7 @@ import { initialState } from "../../index"; import { State } from "../../types"; -import { getIsNetworkedFile, getUiDisplayDataTree } from "."; +import { getIsNetworkedFile } from "."; describe("trajectory composed selectors", () => { describe("getIsNetworkedFile", () => { @@ -37,69 +37,4 @@ describe("trajectory composed selectors", () => { expect(getIsNetworkedFile(state)).toBe(true); }); }); - - describe("getUiDisplayDataTree", () => { - it("returns an empty array if ui display data is empty", () => { - expect(getUiDisplayDataTree(initialState)).toStrictEqual([]); - }); - it("correctly maps agent display info to an array of display data", () => { - const state: State = { - ...initialState, - trajectory: { - ...initialState.trajectory, - defaultUIData: [ - { - name: "agent1", - displayStates: [], - color: "#bbbbbb", - }, - { - name: "agent2", - color: "#aaaaaa", - displayStates: [ - { - name: "state1", - id: "state1_id", - color: "#000000", - }, - { - name: "state2", - id: "state2_id", - color: "#000000", - }, - ], - }, - ], - }, - }; - - const expected = [ - { - title: "agent1", - key: "agent1", - children: [], - color: "#bbbbbb", - }, - { - title: "agent2", - key: "agent2", - color: "#aaaaaa", - children: [ - { - color: "#000000", - label: "state1", - value: "state1_id", - }, - { - color: "#000000", - label: "state2", - value: "state2_id", - }, - ], - }, - ]; - - expect(getUiDisplayDataTree(state)).toStrictEqual(expected); - }); - }); });