From b755bce8f08f8d56d3ce5ef594cc03735c7a7b51 Mon Sep 17 00:00:00 2001 From: BrianWhitneyAI Date: Tue, 2 Apr 2024 15:17:14 -0700 Subject: [PATCH 1/5] feature/persistant-columns --- .../services/PersistentConfigService/index.ts | 4 +++ packages/core/state/index.ts | 8 +++++ packages/core/state/metadata/logics.ts | 32 +++++++++++-------- packages/desktop/src/renderer/index.tsx | 12 +++++-- .../PersistentConfigServiceElectron.ts | 20 ++++++++++++ 5 files changed, 60 insertions(+), 16 deletions(-) diff --git a/packages/core/services/PersistentConfigService/index.ts b/packages/core/services/PersistentConfigService/index.ts index fac029820..541b320fd 100644 --- a/packages/core/services/PersistentConfigService/index.ts +++ b/packages/core/services/PersistentConfigService/index.ts @@ -1,9 +1,12 @@ +import { AnnotationResponse } from "../AnnotationService"; + /** * Keys for the data saved by this service */ export enum PersistedConfigKeys { AllenMountPoint = "ALLEN_MOUNT_POINT", CsvColumns = "CSV_COLUMNS", + DisplayAnnotations = "DISPLAY_ANNOTATIONS", ImageJExecutable = "IMAGE_J_EXECUTABLE", // Deprecated HasUsedApplicationBefore = "HAS_USED_APPLICATION_BEFORE", UserSelectedApplications = "USER_SELECTED_APPLICATIONS", @@ -16,6 +19,7 @@ export interface UserSelectedApplication { export interface PersistedConfig { [PersistedConfigKeys.CsvColumns]?: string[]; + [PersistedConfigKeys.DisplayAnnotations]?: AnnotationResponse[]; [PersistedConfigKeys.ImageJExecutable]?: string; // Deprecated [PersistedConfigKeys.HasUsedApplicationBefore]?: boolean; [PersistedConfigKeys.UserSelectedApplications]?: UserSelectedApplication[]; diff --git a/packages/core/state/index.ts b/packages/core/state/index.ts index f12904f05..cb4e02e15 100644 --- a/packages/core/state/index.ts +++ b/packages/core/state/index.ts @@ -8,6 +8,7 @@ import { PersistedConfig, PersistedConfigKeys } from "../services/PersistentConf import interaction, { InteractionStateBranch } from "./interaction"; import metadata, { MetadataStateBranch } from "./metadata"; import selection, { SelectionStateBranch } from "./selection"; +import Annotation from "../entity/Annotation"; export { interaction, metadata, selection }; @@ -63,6 +64,13 @@ export function createReduxStore(options: CreateStoreOptions = {}) { userSelectedApplications: persistedConfig && persistedConfig[PersistedConfigKeys.UserSelectedApplications], }, + selection: { + displayAnnotations: + persistedConfig && + persistedConfig[PersistedConfigKeys.DisplayAnnotations]?.map( + (annotation) => new Annotation(annotation) + ), + }, }); return configureStore({ middleware: [...(options.middleware || []), ...(middleware || [])], diff --git a/packages/core/state/metadata/logics.ts b/packages/core/state/metadata/logics.ts index 2cb3707df..81b774ba8 100644 --- a/packages/core/state/metadata/logics.ts +++ b/packages/core/state/metadata/logics.ts @@ -20,6 +20,7 @@ const requestAnnotations = createLogic({ const { getState, httpClient } = deps; const applicationVersion = interaction.selectors.getApplicationVersion(getState()); const baseUrl = interaction.selectors.getFileExplorerServiceBaseUrl(getState()); + const displayAnnotations = selection.selectors.getAnnotationsToDisplay(getState()); const annotationService = new AnnotationService({ applicationVersion, baseUrl, @@ -28,21 +29,26 @@ const requestAnnotations = createLogic({ try { const annotations = await annotationService.fetchAnnotations(); - const defaultDisplayAnnotations = compact([ - find( - TOP_LEVEL_FILE_ANNOTATIONS, - (annotation) => annotation.name === AnnotationName.FILE_NAME - ), - find(annotations, (annotation) => annotation.name === AnnotationName.KIND), - find(annotations, (annotation) => annotation.name === AnnotationName.TYPE), - find( - TOP_LEVEL_FILE_ANNOTATIONS, - (annotation) => annotation.name === AnnotationName.FILE_SIZE - ), - ]); + + if (!displayAnnotations.length) { + const defaultDisplayAnnotations = compact([ + find( + TOP_LEVEL_FILE_ANNOTATIONS, + (annotation) => annotation.name === AnnotationName.FILE_NAME + ), + find(annotations, (annotation) => annotation.name === AnnotationName.KIND), + find(annotations, (annotation) => annotation.name === AnnotationName.TYPE), + find( + TOP_LEVEL_FILE_ANNOTATIONS, + (annotation) => annotation.name === AnnotationName.FILE_SIZE + ), + ]); + dispatch( + selection.actions.selectDisplayAnnotation(defaultDisplayAnnotations, true) + ); + } dispatch(receiveAnnotations(annotations)); - dispatch(selection.actions.selectDisplayAnnotation(defaultDisplayAnnotations, true)); } catch (err) { console.error("Failed to fetch annotations", err); } finally { diff --git a/packages/desktop/src/renderer/index.tsx b/packages/desktop/src/renderer/index.tsx index adae2098f..e70324378 100644 --- a/packages/desktop/src/renderer/index.tsx +++ b/packages/desktop/src/renderer/index.tsx @@ -10,7 +10,7 @@ import { Provider } from "react-redux"; import FmsFileExplorer from "../../../core/App"; import { PersistedConfigKeys } from "../../../core/services"; -import { createReduxStore, interaction } from "../../../core/state"; +import { createReduxStore, interaction, selection } from "../../../core/state"; import ApplicationInfoServiceElectron from "../services/ApplicationInfoServiceElectron"; import ExecutionEnvServiceElectron from "../services/ExecutionEnvServiceElectron"; @@ -73,16 +73,22 @@ const store = createReduxStore({ store.subscribe(() => { const state = store.getState(); const csvColumns = interaction.selectors.getCsvColumns(state); + const displayAnnotations = selection.selectors.getAnnotationsToDisplay(state); const userSelectedApplications = interaction.selectors.getUserSelectedApplications(state); const hasUsedApplicationBefore = interaction.selectors.hasUsedApplicationBefore(state); const appState = { [PersistedConfigKeys.CsvColumns]: csvColumns, + [PersistedConfigKeys.DisplayAnnotations]: displayAnnotations.map((annotation) => ({ + annotationDisplayName: annotation.displayName, + annotationName: annotation.name, + description: annotation.description, + type: annotation.type, + })), [PersistedConfigKeys.UserSelectedApplications]: userSelectedApplications, }; if (JSON.stringify(appState) !== JSON.stringify(persistentConfigService.getAll())) { persistentConfigService.persist({ - [PersistedConfigKeys.CsvColumns]: csvColumns, - [PersistedConfigKeys.UserSelectedApplications]: userSelectedApplications, + ...appState, [PersistedConfigKeys.HasUsedApplicationBefore]: hasUsedApplicationBefore, }); } diff --git a/packages/desktop/src/services/PersistentConfigServiceElectron.ts b/packages/desktop/src/services/PersistentConfigServiceElectron.ts index 17815a6cd..ee3ca6216 100644 --- a/packages/desktop/src/services/PersistentConfigServiceElectron.ts +++ b/packages/desktop/src/services/PersistentConfigServiceElectron.ts @@ -22,6 +22,26 @@ const OPTIONS: Options> = { type: "string", }, }, + [PersistedConfigKeys.DisplayAnnotations]: { + type: "array", + items: { + type: "object", + properties: { + annotationDisplayName: { + type: "string", + }, + annotationName: { + type: "string", + }, + description: { + type: "string", + }, + type: { + type: "string", + }, + }, + }, + }, // ImageJExecutable is Deprecated [PersistedConfigKeys.ImageJExecutable]: { type: "string", From 13a90d06761f5664cbbdf385ad7ea1988bb70070 Mon Sep 17 00:00:00 2001 From: BrianWhitneyAI Date: Wed, 3 Apr 2024 15:05:07 -0700 Subject: [PATCH 2/5] bugfix/deselect-not-persisting --- packages/core/state/selection/actions.ts | 8 ++++++-- packages/core/state/selection/reducer.ts | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/core/state/selection/actions.ts b/packages/core/state/selection/actions.ts index d30ff0a62..24dbdef3c 100644 --- a/packages/core/state/selection/actions.ts +++ b/packages/core/state/selection/actions.ts @@ -157,7 +157,9 @@ export const DESELECT_DISPLAY_ANNOTATION = makeConstant( ); export interface DeselectDisplayAnnotationAction { - payload: Annotation | Annotation[]; + payload: { + annotation: Annotation | Annotation[]; + }; type: string; } @@ -165,7 +167,9 @@ export function deselectDisplayAnnotation( annotation: Annotation | Annotation[] ): DeselectDisplayAnnotationAction { return { - payload: annotation, + payload: { + annotation, + }, type: DESELECT_DISPLAY_ANNOTATION, }; } diff --git a/packages/core/state/selection/reducer.ts b/packages/core/state/selection/reducer.ts index 0148d01a2..7ff01b00e 100644 --- a/packages/core/state/selection/reducer.ts +++ b/packages/core/state/selection/reducer.ts @@ -1,5 +1,5 @@ import { makeReducer } from "@aics/redux-utils"; -import { castArray, difference, omit, without } from "lodash"; +import { castArray, difference, omit } from "lodash"; import interaction from "../interaction"; import { AnnotationName, PAST_YEAR_FILTER } from "../../constants"; @@ -123,9 +123,9 @@ export default makeReducer( sortColumn: action.payload, }), [DESELECT_DISPLAY_ANNOTATION]: (state, action) => { - const displayAnnotations = without( - state.displayAnnotations, - ...castArray(action.payload) + // remove deselected annotation from state.displayAnnotations + const displayAnnotations = state.displayAnnotations.filter( + (annotation) => annotation.name !== action.payload.annotation.name ); const columnWidthsToPrune = difference( From 21ee35bc53ef0cb252e1995aa28bcf8a216028f1 Mon Sep 17 00:00:00 2001 From: BrianWhitneyAI Date: Thu, 4 Apr 2024 15:15:18 -0700 Subject: [PATCH 3/5] add persistant display annotations test --- .../PersistentConfigServiceElectron.test.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/desktop/src/services/test/PersistentConfigServiceElectron.test.ts b/packages/desktop/src/services/test/PersistentConfigServiceElectron.test.ts index 5d05ef1b0..ff2d1667f 100644 --- a/packages/desktop/src/services/test/PersistentConfigServiceElectron.test.ts +++ b/packages/desktop/src/services/test/PersistentConfigServiceElectron.test.ts @@ -45,6 +45,17 @@ describe(`${RUN_IN_RENDERER} PersistentConfigServiceElectron`, () => { name: "ZEN", }, ]; + + const expectedDisplayAnnotations = [ + { + annotationDisplayName: "Foo", + annotationName: "foo", + description: "foo-long", + type: "string", + units: "string", + }, + ]; + service.persist(PersistedConfigKeys.AllenMountPoint, expectedAllenMountPoint); service.persist(PersistedConfigKeys.CsvColumns, expectedCsvColumns); service.persist(PersistedConfigKeys.ImageJExecutable, expectedImageJExecutable); @@ -53,12 +64,15 @@ describe(`${RUN_IN_RENDERER} PersistentConfigServiceElectron`, () => { expectedHasUsedApplicationBefore ); service.persist(PersistedConfigKeys.UserSelectedApplications, expectedUserSelectedApps); + service.persist(PersistedConfigKeys.DisplayAnnotations, expectedDisplayAnnotations); + const expectedConfig = { [PersistedConfigKeys.AllenMountPoint]: expectedAllenMountPoint, [PersistedConfigKeys.CsvColumns]: expectedCsvColumns, [PersistedConfigKeys.ImageJExecutable]: expectedImageJExecutable, [PersistedConfigKeys.HasUsedApplicationBefore]: expectedHasUsedApplicationBefore, [PersistedConfigKeys.UserSelectedApplications]: expectedUserSelectedApps, + [PersistedConfigKeys.DisplayAnnotations]: expectedDisplayAnnotations, }; // Act @@ -85,6 +99,15 @@ describe(`${RUN_IN_RENDERER} PersistentConfigServiceElectron`, () => { name: "ImageJ/Fiji", }, ], + [PersistedConfigKeys.DisplayAnnotations]: [ + { + annotationDisplayName: "Foo", + annotationName: "foo", + description: "foo-long", + type: "string", + units: "string", + }, + ], }; // Act From 61f7cbfb5dbd0e83ea6f8959e80caaaaefab87a7 Mon Sep 17 00:00:00 2001 From: BrianWhitneyAI Date: Fri, 5 Apr 2024 11:27:38 -0700 Subject: [PATCH 4/5] adjust dispatch ordering --- packages/core/state/metadata/logics.ts | 46 +++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/core/state/metadata/logics.ts b/packages/core/state/metadata/logics.ts index 81b774ba8..1c93a299b 100644 --- a/packages/core/state/metadata/logics.ts +++ b/packages/core/state/metadata/logics.ts @@ -10,6 +10,7 @@ import { } from "./actions"; import { AnnotationName, TOP_LEVEL_FILE_ANNOTATIONS } from "../../constants"; import AnnotationService from "../../services/AnnotationService"; +import Annotation from "../../entity/Annotation"; /** * Interceptor responsible for turning REQUEST_ANNOTATIONS action into a network call for available annotations. Outputs @@ -27,33 +28,32 @@ const requestAnnotations = createLogic({ httpClient, }); - try { - const annotations = await annotationService.fetchAnnotations(); - - if (!displayAnnotations.length) { - const defaultDisplayAnnotations = compact([ - find( - TOP_LEVEL_FILE_ANNOTATIONS, - (annotation) => annotation.name === AnnotationName.FILE_NAME - ), - find(annotations, (annotation) => annotation.name === AnnotationName.KIND), - find(annotations, (annotation) => annotation.name === AnnotationName.TYPE), - find( - TOP_LEVEL_FILE_ANNOTATIONS, - (annotation) => annotation.name === AnnotationName.FILE_SIZE - ), - ]); - dispatch( - selection.actions.selectDisplayAnnotation(defaultDisplayAnnotations, true) - ); - } + let annotations: Annotation[] = []; - dispatch(receiveAnnotations(annotations)); + try { + annotations = await annotationService.fetchAnnotations(); } catch (err) { console.error("Failed to fetch annotations", err); - } finally { - done(); } + + dispatch(receiveAnnotations(annotations)); + + if (!displayAnnotations.length) { + const defaultDisplayAnnotations = compact([ + find( + TOP_LEVEL_FILE_ANNOTATIONS, + (annotation) => annotation.name === AnnotationName.FILE_NAME + ), + find(annotations, (annotation) => annotation.name === AnnotationName.KIND), + find(annotations, (annotation) => annotation.name === AnnotationName.TYPE), + find( + TOP_LEVEL_FILE_ANNOTATIONS, + (annotation) => annotation.name === AnnotationName.FILE_SIZE + ), + ]); + dispatch(selection.actions.selectDisplayAnnotation(defaultDisplayAnnotations, true)); + } + done(); }, type: REQUEST_ANNOTATIONS, }); From f3b21abbf790b9287163be67ba25e771407927e0 Mon Sep 17 00:00:00 2001 From: BrianWhitneyAI Date: Fri, 5 Apr 2024 15:06:19 -0700 Subject: [PATCH 5/5] comment resolution --- packages/core/state/metadata/logics.ts | 5 +++-- packages/desktop/src/renderer/index.tsx | 9 ++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/core/state/metadata/logics.ts b/packages/core/state/metadata/logics.ts index 1c93a299b..f12ef8f4e 100644 --- a/packages/core/state/metadata/logics.ts +++ b/packages/core/state/metadata/logics.ts @@ -32,12 +32,13 @@ const requestAnnotations = createLogic({ try { annotations = await annotationService.fetchAnnotations(); + dispatch(receiveAnnotations(annotations)); } catch (err) { console.error("Failed to fetch annotations", err); + done(); + return; } - dispatch(receiveAnnotations(annotations)); - if (!displayAnnotations.length) { const defaultDisplayAnnotations = compact([ find( diff --git a/packages/desktop/src/renderer/index.tsx b/packages/desktop/src/renderer/index.tsx index e70324378..b8c63908e 100644 --- a/packages/desktop/src/renderer/index.tsx +++ b/packages/desktop/src/renderer/index.tsx @@ -75,7 +75,6 @@ store.subscribe(() => { const csvColumns = interaction.selectors.getCsvColumns(state); const displayAnnotations = selection.selectors.getAnnotationsToDisplay(state); const userSelectedApplications = interaction.selectors.getUserSelectedApplications(state); - const hasUsedApplicationBefore = interaction.selectors.hasUsedApplicationBefore(state); const appState = { [PersistedConfigKeys.CsvColumns]: csvColumns, [PersistedConfigKeys.DisplayAnnotations]: displayAnnotations.map((annotation) => ({ @@ -85,13 +84,9 @@ store.subscribe(() => { type: annotation.type, })), [PersistedConfigKeys.UserSelectedApplications]: userSelectedApplications, + [PersistedConfigKeys.HasUsedApplicationBefore]: true, }; - if (JSON.stringify(appState) !== JSON.stringify(persistentConfigService.getAll())) { - persistentConfigService.persist({ - ...appState, - [PersistedConfigKeys.HasUsedApplicationBefore]: hasUsedApplicationBefore, - }); - } + persistentConfigService.persist(appState); }); function renderFmsFileExplorer() {